2021.07.14【NOIP提高B组】模拟 总结

这篇博客概述了2021年7月14日NOIP提高B组的四个题目解决方案:1) 贪心算法求直径;2) 利用网格图解决高度差限制问题;3) 二分查找与转移策略;4) 求二分图最大独立集转化为最小割问题。核心算法涉及网络流、贪心法和图论技巧。
摘要由CSDN通过智能技术生成

2021.07.14【NOIP提高B组】模拟 总结

第一题:贪心,每一次的直径会跟上一次的直径有关,取个最大值即可。

第二题:发现相邻两个的高度绝对值差小于等于 1 1 1。考虑在网格图上做。用总方案数减去不合法的方案数。注意对称轴为 − 1 -1 1

第三题:二分。设 f i , j f_{i,j} fi,j表示前 i i i个做了 j j j 1 1 1最多做多少 2 2 2,贪心转移。

第四题:

题目大意是求二分图最大独立集。

我们把权值加上 i n f inf inf后,就是求二分图最大权独立集,这样就不用考虑点数了。

考虑染色出二分图。然后建图:

s s s向二分图左边的部分连一条容量为点权加上 i n f inf inf的边。

二分图的右边部分向 t t t连一条容量为点权加上 i n f inf inf的边。

然后二分图的边连起来,容量为 I n f Inf Inf(与 i n f inf inf不同)。

对于这种二元关系的题目。就是求这个网络的最小割。

但答案是总的权值减去最小割。为什么要用总的权值减去最小割?考虑一条边如果被割了,那么相当于把点移除集合。如果不割,相当于留在 T T T中。所以求的答案, T T T的最大点权和,为不割的边的边权和。不割也就是总的权值减去最小割。

寻找答案:

  • 本题的算法:考虑从 s s s开始搜索,在残量网络上找仍可以流的边,然后找到 S S S集合。枚举边集,看一看这条边是否过两个集合。这些边是最小割。可知最小割的边的一端一定连着 s , t s,t s,t,那么我们只要把另一端标记,另一端即为在最小割中的结点。答案求不在最小割中的点。记录一下就可以了。注意时间复杂度为 O ( n 2 m ) O(n^2m) O(n2m)

    #include<bits/stdc++.h>
    #define fo(a,b,c) for (ll a=b;a<=c;a++)
    using namespace std;
    typedef long long ll;
    const ll inf=1e9;
    const ll inf2=1e18;
    ll n,m,w[305],la[305],la2[305],fr[200000],to[200000],ne[200000],cap[200000],flow[200005],cnt=1,s,t,col[305];
    ll g[305][305];
    void color(ll x,ll c){
    	col[x]=c;
    	fo(i,1,g[x][0]){
    		ll y=g[x][i];
    		if (!col[y]){
    			color(y,3-c);
    		}
    	}
    }
    void add(ll x,ll y,ll c){
    	++cnt;
    	fr[cnt]=x;
    	to[cnt]=y;
    	cap[cnt]=c;
    	flow[cnt]=0;
    	ne[cnt]=la[x];
    	la[x]=cnt;
    }
    queue<ll>q,q2;
    ll a[305],vis[305];
    ll bfs(){
    	memset(vis,0,sizeof(vis));
    	vis[s]=1;
    	memset(a,0,sizeof(a));
    	q=q2;
    	q.push(s);
    	while (!q.empty()){
    		ll x=q.front();
    		q.pop();
    		for (ll i=la[x];i;i=ne[i]){
    			ll y=to[i];
    			if (cap[i]>flow[i]&&(!vis[y])){
    				vis[y]=1;
    				a[y]=a[x]+1;
    				q.push(y);
    			}
    		}
    	}
    	return a[t];
    }
    ll dfs(ll x,ll nowflow){
    	if (nowflow==0||x==t) return nowflow;
    	ll sum=0;
    	for (ll &i=la2[x];i;i=ne[i]){
    		ll y=to[i];
    		if (a[x]+1==a[y]){
    			int f=dfs(y,min(nowflow,cap[i]-flow[i]));
    			if (f){
    				sum+=f;
    				nowflow-=f;
    				flow[i]+=f;
    				flow[i^1]-=f;
    				if (!nowflow) break;
    			}
    		}
    	}
    	return sum;
    }
    ll dinic(ll sum){
    	ll ans=sum;
    	while (bfs()){
    		fo(i,1,t) la2[i]=la[i];
    		ans-=dfs(s,inf2);
    	}
    	return ans;
    }
    ll ins[305],ch[305];
    void find(){
    	q=q2;
    	q.push(s);
    	ins[s]=1;
    	while (!q.empty()){
    		int x=q.front();
    		q.pop();
    		for (ll i=la[x];i;i=ne[i]){
    			ll y=to[i];
    			if (cap[i]>flow[i]&&(!ins[y])){
    				ins[y]=1;
    				q.push(y);
    			}
    		}
    	}
    }
    int main(){
    	freopen("graph.in","r",stdin);
    	freopen("graph.out","w",stdout);
    	ll sum=0;
    	scanf("%lld%lld",&n,&m);
    	s=n+1;
    	t=n+2;
    	fo(i,1,n)scanf("%lld",&w[i]),w[i]+=inf,sum+=w[i];
    	fo(i,1,m){
    		ll x,y;
    		scanf("%lld%lld",&x,&y);
    		g[x][++g[x][0]]=y;
    		g[y][++g[y][0]]=x;
    	}
    	fo(i,1,n)if(!col[i])color(i,1);
    	fo(i,1,n){
    		if (col[i]==1){
    			add(s,i,w[i]);
    			add(i,s,0);
    		}
    		if (col[i]==2){
    			add(i,t,w[i]);
    			add(t,i,0);
    		}
    	}
    	fo(i,1,n){
    		if (col[i]==1){
    			fo(j,1,g[i][0]){
    				ll k=g[i][j];
    				add(i,k,inf2);
    				add(k,i,0);
    			}
    		}
    	}
    	ll ans=dinic(sum);
    	find(); 
    	fo(i,2,cnt){
    		if (i&1) continue;
    		if (ins[fr[i]]!=ins[to[i]]){
    			if (fr[i]==s||fr[i]==t) ch[to[i]]=1;
    			if (to[i]==s||to[i]==t) ch[fr[i]]=1;
    		}
    	}
    	ll ans1=0;
    	fo(i,1,n)ans1+=(ch[i]^1);
    	printf("%lld %lld\n",ans1,ans%inf);
    	fo(i,1,n)putchar((ch[i]^1)+'0');
    }
    
  • 一般算法:考虑枚举每条边(容量为 c c c),设原来的最小割为 c u t 1 cut1 cut1,删除这条边后的为 c u t 2 cut2 cut2,如果 c u t 1 − c u t 2 = c cut1-cut2=c cut1cut2=c,那么说明这条边有贡献。那么这条边的某一个端点在最小割中。注意每一次如果满足情况,都要把这条边给删去,并且把原来的最小割更新。注意时间复杂度为 O ( n 2 m 2 ) O(n^2m^2) O(n2m2)

    #include<bits/stdc++.h>
    #define fo(a,b,c) for (ll a=b;a<=c;a++)
    using namespace std;
    typedef long long ll;
    const ll inf=1e9;
    const ll inf2=1e18;
    ll n,m,w[305],la[305],la2[305],bz[200000],fr[200000],to[200000],ne[200000],cap[200000],flow[200005],cnt=1,s,t,col[305];
    ll g[305][305];
    void color(ll x,ll c){
    	col[x]=c;
    	fo(i,1,g[x][0]){
    		ll y=g[x][i];
    		if (!col[y]){
    			color(y,3-c);
    		}
    	}
    }
    void add(ll x,ll y,ll c){
    	++cnt;
    	fr[cnt]=x;
    	to[cnt]=y;
    	cap[cnt]=c;
    	flow[cnt]=0;
    	ne[cnt]=la[x];
    	la[x]=cnt;
    }
    queue<ll>q,q2;
    ll a[305],vis[305];
    ll bfs(){
    	memset(vis,0,sizeof(vis));
    	vis[s]=1;
    	memset(a,0,sizeof(a));
    	q=q2;
    	q.push(s);
    	while (!q.empty()){
    		ll x=q.front();
    		q.pop();
    		for (ll i=la[x];i;i=ne[i]){
    			ll y=to[i];
    			if (cap[i]>flow[i]&&bz[i]==0&&(!vis[y])){
    				vis[y]=1;
    				a[y]=a[x]+1;
    				q.push(y);
    			}
    		}
    	}
    	return a[t];
    }
    ll dfs(ll x,ll nowflow){
    	if (nowflow==0||x==t) return nowflow;
    	ll sum=0;
    	for (ll &i=la2[x];i;i=ne[i]){
    		ll y=to[i];
    		if (a[x]+1==a[y]&&bz[i]==0){
    			int f=dfs(y,min(nowflow,cap[i]-flow[i]));
    			if (f){
    				sum+=f;
    				nowflow-=f;
    				flow[i]+=f;
    				flow[i^1]-=f;
    				if (!nowflow) break;
    			}
    		}
    	}
    	return sum;
    }
    ll dinic(ll sum){
    	ll ans=sum;
    	while (bfs()){
    		fo(i,1,t) la2[i]=la[i];
    		ans-=dfs(s,inf2);
    	}
    	return ans;
    }
    ll ins[305],ch[305];
    void find(){
    	q=q2;
    	q.push(s);
    	ins[s]=1;
    	while (!q.empty()){
    		int x=q.front();
    		q.pop();
    		for (ll i=la[x];i;i=ne[i]){
    			ll y=to[i];
    			if (cap[i]>flow[i]&&(!ins[y])){
    				ins[y]=1;
    				q.push(y);
    			}
    		}
    	}
    }
    int main(){
    	freopen("graph.in","r",stdin);
    	freopen("graph.out","w",stdout);
    	ll sum=0;
    	scanf("%lld%lld",&n,&m);
    	s=n+1;
    	t=n+2;
    	fo(i,1,n)scanf("%lld",&w[i]),w[i]+=inf,sum+=w[i];
    	fo(i,1,m){
    		ll x,y;
    		scanf("%lld%lld",&x,&y);
    		g[x][++g[x][0]]=y;
    		g[y][++g[y][0]]=x;
    	}
    	fo(i,1,n)if(!col[i])color(i,1);
    	fo(i,1,n){
    		if (col[i]==1){
    			add(s,i,w[i]);
    			add(i,s,0);
    		}
    		if (col[i]==2){
    			add(i,t,w[i]);
    			add(t,i,0);
    		}
    	}
    	fo(i,1,n){
    		if (col[i]==1){
    			fo(j,1,g[i][0]){
    				ll k=g[i][j];
    				add(i,k,inf2);
    				add(k,i,0);
    			}
    		}
    	}
    	ll ans=dinic(sum);
    	ll ansa=ans;
    	fo(i,2,cnt){
    		if (i&1) continue;
    		ll ck=0;
    		bz[i]=bz[i^1]=1;
    		memset(flow,0,sizeof(flow));
    		ll cut2=dinic(sum);
    		if (abs(ans-cut2)==cap[i]){
    			ck=1;
    			ans=cut2;
    		}
    		else{
    			bz[i]=bz[i^1]=0;
    		}
    		if (ck){
    			if (fr[i]==s||fr[i]==t) ch[to[i]]=1;
    			if (to[i]==s||to[i]==t) ch[fr[i]]=1;
    		}
    	}
    	ll ans1=0;
    	fo(i,1,n)ans1+=(ch[i]^1);
    	printf("%lld %lld\n",ans1,ansa%inf);
    	fo(i,1,n)putchar((ch[i]^1)+'0');
    }
    
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值