ABC318枚举+贪心 网络流+拆点

F

思维 贪心

每个手长度lil_ili,可以抓住[k−li,k+li][k-l_i,k+l_i][kli,k+li]内的一个东西,kkk是身体所在位置,有和手等量的东西在数轴上,问能抓住所有东西的身体个数?身体必须在整点。

首先可以发现,整个数轴上的点可以分段,每段内要么都合法,要么都不合法。

因为可以这样分段:注意到触手和东西都不多n=100n=100n=100,身体位置不确定,但是东西位置是确定的,你那么可以枚举哪个手抓住了那个东西,这样身体就必须在一个区间内,这些区间的边界,把整个数轴划分成了多个部分。

那么每两个边界之间,移动一下肯定没有任何一个东西会从能拿到,变成拿不到,所以区间内的每个点要么都能拿到n个东西,要么都拿不到。

这样的分界点只有O(n2)O(n^2)O(n2)个,分成O(n2)O(n^2)O(n2)个区间,我们对于每个区间,分别检查能不能拿到所有东西即可,每次检查由于整个区间内的点都是等价的,我们随便选一个区间内的点放头即可。

检查时类似二分的check思路,我们肯定要贪心的决策,最后看能不能满足要求。这里的贪心就是,头确定了,头到每个东西距离did_idi确定了,现在可以排列序列lil_ili,要求li>=dil_i>=d_ili>=di。这是个经典贪心,都升序排序,然后看是不是满足即可,考虑交换论证,排序后的序列交换俩不会更优。

bool check(int k){
	priority_queue<int>q;
	rep(i,1,n){
		q.push(abs(x[i]-k));
	}
	rep(i,1,n){
		if(q.top()>l[i])return 0;
		q.pop();
	}
	return 1;
}
void solve(void){
	cin>>n;
	rep(i,1,n)cin>>x[i];
	rep(i,1,n)cin>>l[i];
	sort(l+1,l+1+n,greater<int>());
	int tot=0;
	rep(i,1,n){
		rep(j,1,n){
			s[++tot]=x[i]-l[j]-1;
			s[++tot]=x[i]+l[j];
		}
	}
	sort(s+1,s+1+tot);
	int ans=0;
	rep(i,2,tot){
		if(check(s[i])){
			ans+=s[i]-s[i-1];
		}
	}
	cout<<ans;
}

G

网络流 拆点

问图上是否存在一个简单路径经过a,b,c

可以考虑从中间那个点出发,就变成了找到两个不相交的简单路径b−a,b−cb-a,b-cba,bc,但是不相交其实非常难办。

常规手段用不了了,考虑一下科技吧,然后我们可以发现这其实等价于从b出发,到ac两个汇点的网络流,最后看最大流是不是2即可,是2就一定能找到一个方案,因为网络流板子已经帮我们处理了一条边能用的次数了,我们只要把流量上限都设成1,就能保证每个边只能用一次,b−a,b−cb-a,b-cba,bc不会用公共边了。

但是简单路径还要求公共点也不能有,这就意味着跑网络流时每个点也只能用一次,网络流模型本身可不保证这一点。

这可以拆点优化,也是个常见的优化思路,即把一个点iii拆成i,i+ni,i+ni,i+n,一个入点一个出点,所有到iii的边连到iii,所有从iii出的边连到i+ni+ni+n,然后w(i,i+n)=1w(i,i+n)=1w(i,i+n)=1,这样就能保证从iii进,i+1i+1i+1出这个事件最多发生一次,即iii点最多被用一次。

最后源点到bbb流量为2,a,ca,ca,c到汇点流量各自为1即可

int h[N * 2], e[N * 6], ne[N * 6], f[N * 6], idx, n, m, a, b, c, S, T, cur[N * 2], d[N * 2];
bool vis[N * 2];
void add(int a, int b, int c) {
	e[idx] = b, ne[idx] = h[a], f[idx] = c, h[a] = idx++;
	e[idx] = a, ne[idx] = h[b], f[idx] = 0, h[b] = idx++;
} 
bool bfs() {
	memset(d, -1, sizeof(d));
	memset(vis, 0, sizeof(vis));
	queue<int> q;
	q.push(S);
	vis[S] = 1, d[S] = 0, cur[S] = h[S];
	while (q.size()) {
		int t = q.front();
		q.pop();
		for (int i = h[t]; ~i; i = ne[i]) {
			int j = e[i];
			if (vis[j] || !f[i]) continue;
			d[j] = d[t] + 1, vis[j] = 1, cur[j] = h[j];
			if (j == T) return 1;
			q.push(j);
		}
	}
	return 0;
}
int dfs(int x, int limit) {
	if (x == T) return limit;
	int flow = 0;
	for (int i = cur[x]; (~i) && flow < limit; i = ne[i]) {
		int j = e[i];
		cur[x] = i;
		if (d[j] != d[x] + 1 || !f[i]) continue;
		int k = dfs(j, min(f[i], limit - flow));
		if (!k) d[j] = -1;
		f[i] -= k;
		f[i ^ 1] += k;
		flow += k;
	}
	return flow;
}
int dinic() {
	int ans = 0, flow;
	while (bfs()) while (flow = dfs(S, 1e9)) ans += flow;
	return ans;
}
void solve(void){
	memset(h,-1,sizeof h);
	cin>>n>>m;
	cin>>a>>b>>c;
	rep(i,1,m){
		int u,v;
		cin>>u>>v;
		add(u+n,v,1);
		add(v+n,u,1);
	}
	S=0,T=2*n+1;
	add(S,b,2);
	add(a+n,T,1);
	add(c+n,T,1);
	rep(i,1,n){
		if(i!=b)add(i,i+n,1);
		else add(i,i+n,2);
	}
	int ans=dinic();
	if(ans==2)cout<<"Yes";
	else cout<<"No";
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值