能力提升综合题单 Part 8.9.2 最小割

1.P1344 [USACO4.4]追查坏牛奶Pollutant Control

第一问求最小割

第二问求最小割边数

一个巧妙的方法就是: *一个比 m m m大的数,答案除,边数模

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn],mm;
int cnt=1,maxflow=0,n,m,s,t;
struct edge{
	int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].w=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
bool bfs(){
	memset(dep,0,sizeof(dep));
    for(int i=0;i<=n+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!! 
    	cur[i]=head[i];
	}
	dep[s]=1;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(!dep[v]&&e[i].w){
				dep[v]=dep[u]+1;
				q.push(v);
				if(v==t)return true;		
			}
		}
	}
	return false;
}
int dfs(int u,int now){
	if(u==t||now==0){
		return now;
	}
	int flow=0,rlow=0;
	for(int i=cur[u];i;i=e[i].nex){
		int v=e[i].v;
		if(e[i].w&&dep[v]==dep[u]+1){
			if(rlow=dfs(v,min(now,e[i].w))){
				flow+=rlow;
				now-=rlow;
				e[i].w-=rlow;
				e[i^1].w+=rlow;
				if(now==0)return flow;
			}
		}
	}
	if(!flow)dep[u]=-1;
	return flow;
}
int dinic(){
	while(bfs()){
		maxflow+=dfs(s,inf);
	}
	return maxflow;
}
signed main(){
	scanf("%lld%lld",&n,&m);
	s=1,t=n;
	int u,v,w;
	for(int i=1;i<=m;i++){
		scanf("%lld%lld%lld",&u,&v,&w);
		add_edge(u,v,w*2020+1);
	}
	int ans=dinic();
	printf("%lld %lld",ans/2020,ans%2020);
	return 0;
}

2.P1345 [USACO5.4]奶牛的电信Telecowmunication

拆点最小割即可

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
const int maxm=1e6+5;
const int inf=0x3f3f3f3f;
int dep[maxn],inque[maxn];
int top=1;
int maxflow,n,m,s,t;
struct edge{
	int v,w,rev;
};
vector<edge>e[2*maxm];
inline void add_edge(int u,int v,int w){
	e[u].push_back((edge){v,w,e[v].size()});
	e[v].push_back((edge){u,0,e[u].size()-1});
}
bool bfs(){
	memset(dep,inf,sizeof(dep));
	memset(inque,0,sizeof(inque));
	dep[s]=0;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		inque[u]=0;
		for(int i=0;i<e[u].size();i++){
			int v=e[u][i].v;
			edge x=e[u][i];
			if(dep[v]>dep[u]+1&&x.w){
				dep[v]=dep[u]+1;
				if(inque[v]==0){
					q.push(v);
					inque[v]=1;
				}
			}
		}
	}
	if(dep[t]!=inf)return true;
	return false;
}
int dfs(int u,int flow){
	int rlow=0;
	if(u==t)return flow;
	for(int i=0;i<e[u].size();i++){
		int v=e[u][i].v;
		if(e[u][i].w&&dep[v]==dep[u]+1){
			if(rlow=dfs(v,min(flow,e[u][i].w))){
				e[u][i].w-=rlow;
				e[v][e[u][i].rev].w+=rlow;
				return rlow;
			}
		}
	}
	return 0;
}
int dinic(){
	int lowflow;
	while(bfs()){
		while(lowflow=dfs(s,inf))maxflow+=lowflow;
	}
	return maxflow;
}
int main(){
	scanf("%d%d%d%d",&n,&m,&s,&t);
	int u,v,w;
	s=s+n;
	for(int i=1;i<=n;i++){
		add_edge(i,i+n,1);
	}
	for(int i=1;i<=m;i++){
		scanf("%d%d",&u,&v);
		add_edge(u+n,v,inf);
		add_edge(v+n,u,inf);
	}
	printf("%d",dinic());
	return 0;
}

3.P2057 [SHOI2007]善意的投票 / [JLOI2010]冠军调查

每一对好朋友之间连双向边,代表想要对方和自己一样的立场,求出最小割即为最小冲突数

#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=10000,maxm=2000005;
int ans1,mm,tot;
int cur[maxn],head[maxn],dep[maxn];
int cnt=1,n,m,s,t;
struct edge{
	int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].w=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
bool bfs(){
	memset(dep,0,sizeof(dep));
    for(int i=0;i<=tot+5;i++){
    	cur[i]=head[i];
	}
	dep[s]=1;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(!dep[v]&&e[i].w){
				dep[v]=dep[u]+1;
				q.push(v);
				if(v==t)return true;		
			}
		}
	}
	return false;
}
int dfs(int u,int now){
	if(u==t||now==0){
		return now;
	}
	int flow=0,rlow=0;
	for(int i=cur[u];i;i=e[i].nex){
		int v=e[i].v;
		if(e[i].w&&dep[v]==dep[u]+1){
			if(rlow=dfs(v,min(now,e[i].w))){
				flow+=rlow;
				now-=rlow;
				e[i].w-=rlow;
				e[i^1].w+=rlow;
				if(now==0)return flow;
			}
		}
	}
	if(!flow)dep[u]=-1;
	return flow;
}
int dinic(){
	int maxflow=0; 
	while(bfs()){
		maxflow+=dfs(s,inf);
	}
	return maxflow;
}
int main(){
	int x;
	scanf("%d%d",&n,&m);
	int u,v,w;
	s=0,t=n+1;
	int ans=0;
	for(int i=1;i<=n;i++){
		scanf("%d",&x);
		add_edge(0,i,x);
		add_edge(i,t,x==1?0:1);
		ans+=1;
	}
	tot=t;
	int x1,x2;
	for(int i=1;i<=m;i++){
		scanf("%d%d",&x1,&x2);
		add_edge(x1,x2,1);
		add_edge(x2,x1,1);
	}
	printf("%d",dinic());
	return 0;
}

4.P2598 [ZJOI2009]狼和羊的故事

所有的点跟自己周围的点连边

源点跟1连边

2跟汇点连边

求最小割

#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
const int dx[]={1,-1,0,0};
const int dy[]={0,0,-1,1};
int cnt=1,n,m,s,t;
struct edge{
	int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].w=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
bool bfs(){
	memset(dep,0,sizeof(dep));
    for(int i=0;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!! 
    	cur[i]=head[i];
	}
	dep[s]=1;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(!dep[v]&&e[i].w){
				dep[v]=dep[u]+1;
				q.push(v);
				if(v==t)return true;		
			}
		}
	}
	return false;
}
int dfs(int u,int now){
	if(u==t||now==0){
		return now;
	}
	int flow=0,rlow=0;
	for(int i=cur[u];i;i=e[i].nex){
		int v=e[i].v;
		if(e[i].w&&dep[v]==dep[u]+1){
			if(rlow=dfs(v,min(now,e[i].w))){
				flow+=rlow;
				now-=rlow;
				e[i].w-=rlow;
				e[i^1].w+=rlow;
				if(now==0)return flow;
			}
		}
	}
	if(!flow)dep[u]=-1;
	return flow;
}
int dinic(){
	int maxflow=0;
	while(bfs()){
		maxflow+=dfs(s,inf);
	}
	return maxflow;
}
inline int id(int x,int y){
	return (x-1)*m+y;
}
int f[maxn],num[maxn]; 
int main(){
	scanf("%d%d",&n,&m);
	int u,v,w;
	s=0,t=n*m+1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%d",&f[id(i,j)]);
			if(f[id(i,j)]==1){
				add_edge(s,id(i,j),inf);
			}
			if(f[id(i,j)]==2){
				add_edge(id(i,j),t,inf);
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			for(int k=0;k<4;k++){
				int xx=i+dx[k];
				int yy=j+dy[k];
				if(xx>=1&&yy<=m&&xx<=n&&yy>=1)add_edge(id(i,j),id(xx,yy),1);
			}
		}
	}
	printf("%d",dinic());
	return 0;
}

5.P2774 方格取数问题

相邻的点为不同颜色

汇点向白点连边, w = w= w=点权

黑点向汇点连边, w = w= w=点权

白点向相邻的黑点连边, w = i n f w=inf w=inf

求最小割

其实也可以看做最大权闭合子图

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
const int dx[]={1,-1,0,0};
const int dy[]={0,0,-1,1};
int cnt=1,n,m,s,t;
struct edge{
	int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].w=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
bool bfs(){
	memset(dep,0,sizeof(dep));
    for(int i=0;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!! 
    	cur[i]=head[i];
	}
	dep[s]=1;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(!dep[v]&&e[i].w){
				dep[v]=dep[u]+1;
				q.push(v);
				if(v==t)return true;		
			}
		}
	}
	return false;
}
int dfs(int u,int now){
	if(u==t||now==0){
		return now;
	}
	int flow=0,rlow=0;
	for(int i=cur[u];i;i=e[i].nex){
		int v=e[i].v;
		if(e[i].w&&dep[v]==dep[u]+1){
			if(rlow=dfs(v,min(now,e[i].w))){
				flow+=rlow;
				now-=rlow;
				e[i].w-=rlow;
				e[i^1].w+=rlow;
				if(now==0)return flow;
			}
		}
	}
	if(!flow)dep[u]=-1;
	return flow;
}
int dinic(){
	int maxflow=0;
	while(bfs()){
		maxflow+=dfs(s,inf);
	}
	return maxflow;
}
inline int id(int x,int y){
	return (x-1)*m+y;
}
int f[maxn],se[maxn],sum; 
signed main(){
	scanf("%lld%lld",&n,&m);
	int u,v,w;
	s=0,t=n*m+1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%lld",&f[id(i,j)]);
			sum+=f[id(i,j)];
			if((i+j)%2==1){
				se[id(i,j)]=1;
				add_edge(s,id(i,j),f[id(i,j)]);
			}
			else if((i+j)%2==0){
				add_edge(id(i,j),t,f[id(i,j)]);
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if((i+j)%2==1){
			int dd=id(i,j);
			int ff=f[dd];
//			printf("%d ",ff);
			for(int k=0;k<4;k++){
				int xx=i+dx[k];
				int yy=j+dy[k];
				if(xx>=1&&yy<=m&&xx<=n&&yy>=1){
					add_edge(id(i,j),id(xx,yy),inf);
				}
			}
	    	}
		}
	}
	printf("%lld",sum-dinic());
	return 0;
}

6.P4126 [AHOI2009]最小割

问题1:求最小割的可行边

解法:两点不属于同一连通分量,且此边满流

if(e[i].w==0&&col[u]!=col[v]){
	xx=1;
}
else xx=0;

问题2:求最小割的必要边

解法:在1的条件下,两点分别和源点和汇点属于同一强连通分量

if(e[i].w==0&&col[u]==col[s]&&col[v]==col[t]&&col[u]!=col[v]){
	yy=1;
}
else yy=0;

完整代码:

#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=4005,maxm=2e5+5;
int cur[maxn],head[maxn],dep[maxn];
int cnt=1,n,m,s,t,qq,cnt2=1;
struct edge{
	int u,v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].u=u;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].w=0;
	e[cnt].u=u;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
bool bfs(){
	memset(dep,0,sizeof(dep));
    memcpy(cur,head,sizeof head);
	dep[s]=1;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(!dep[v]&&e[i].w){
				dep[v]=dep[u]+1;
				q.push(v);
				if(v==t)return true;		
			}
		}
	}
	return false;
}
int dfs(int u,int now){
	if(u==t||now==0){
		return now;
	}
	int flow=0,rlow=0;
	for(int i=cur[u];i;i=e[i].nex){
		int v=e[i].v;
		if(e[i].w&&dep[v]==dep[u]+1){
			if(rlow=dfs(v,min(now,e[i].w))){
				flow+=rlow;
				now-=rlow;
				e[i].w-=rlow;
				e[i^1].w+=rlow;
				if(now==0)return flow;
			}
		}
	}
	if(!flow)dep[u]=-1;
	return flow;
}
int dinic(){
	int maxflow=0;
	while(bfs()){
		maxflow+=dfs(s,inf);
	}
	return maxflow;
}
namespace Tarjan {
	std::vector<int>v[maxn];
	int dfn[maxn],low[maxn],col[maxn],st[maxn],f[maxn];
	int top,p,cnt,mid,kk;
	std::pair<int,int>ans[60005];
	void tarjan(int x) {
		dfn[x]=low[x]=++cnt;
		f[x]=1,st[++top]=x;
		for(int i=0;i<v[x].size();++i) {
			int j=v[x][i];
			if(!dfn[j]) tarjan(j),low[x]=min(low[x],low[j]);
			else if(f[j]) low[x]=min(low[x],dfn[j]);
		}
		if(dfn[x]==low[x]) {
			++p;
			do {
				mid=st[top--];
				f[mid]=0;
				col[mid]=p;
			}while(mid!=x);
		}
	}
	void solve() {
		int qq=0;
		for(int i=1;i<=n;i++)
			for(int j=head[i];j;j=e[j].nex)
			    if(e[j].w) v[i].push_back(e[j].v);
		for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
		for(int i=2;i<kk;i+=2) {
			int xx,yy;
			int u=e[i].u;
			int v=e[i].v;
			if(e[i].w==0&&col[u]!=col[v]){
				xx=1;
			}
			else xx=0;
			if(e[i].w==0&&col[u]==col[s]&&col[v]==col[t]&&col[u]!=col[v]){
				yy=1;
			}
			else yy=0;
			ans[++qq]=make_pair(xx,yy);
		}
		for(int i=1;i<=qq;i++){
			printf("%d %d\n",ans[i].first,ans[i].second);
		}
	}
}
signed main(){
	scanf("%d%d%d%d",&n,&m,&s,&t);
	int u,v,w;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&u,&v,&w);
		add_edge(u,v,w);
	//	cout<<cnt<<endl;
	}
	int yaoyao=dinic();
	Tarjan::kk=cnt;
	Tarjan::solve();
	return 0;
}

7.P5039 [SHOI2010]最小生成树

题意转化:选择一条边问增加多少次边权能让指定边成为最小生成树的必须边

选择所有边权小于指定边的边,重新赋权为 c [ l a b ] − c [ i ] + 1 c[lab]-c[i]+1 c[lab]c[i]+1

求出最小割,即最少操作多少次使图不连通,指定边就成为了最小生成树的必要边

#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=2005,maxm=20005;
int cur[maxn],head[maxn],dep[maxn];
int cnt=1,n,m,s,t;
struct edge{
	int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].nex=head[u];
	head[u]=cnt;
	e[++cnt].v=u;
	e[cnt].w=0;
	e[cnt].nex=head[v];
	head[v]=cnt;
}
bool bfs(){
	memset(dep,0,sizeof(dep));
    for(int i=1;i<=n;i++){//如果要拆点或者其他的一定记得把范围开大点!!! 
    	cur[i]=head[i];
	}
	dep[s]=1;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nex){
			int v=e[i].v;
			if(!dep[v]&&e[i].w){
				dep[v]=dep[u]+1;
				q.push(v);
				if(v==t)return true;		
			}
		}
	}
	return false;
}
int dfs(int u,int now){
	if(u==t||now==0){
		return now;
	}
	int flow=0,rlow=0;
	for(int i=cur[u];i;i=e[i].nex){
		int v=e[i].v;
		if(e[i].w&&dep[v]==dep[u]+1){
			if(rlow=dfs(v,min(now,e[i].w))){
				flow+=rlow;
				now-=rlow;
				e[i].w-=rlow;
				e[i^1].w+=rlow;
				if(now==0)return flow;
			}
		}
	}
	if(!flow)dep[u]=-1;
	return flow;
}
int dinic(){
	int maxflow=0;
	while(bfs()){
		maxflow+=dfs(s,inf);
	}
	return maxflow;
}
int a[maxn],b[maxn],c[maxn];
int main(){
	int lab; 
	scanf("%d%d%d",&n,&m,&lab);
	int u,v,w;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&a[i],&b[i],&c[i]);
	}
	s=a[lab];
	t=b[lab];
	for(int i=1;i<=m;i++){
		if(i==lab)continue;
		if(c[i]<=c[lab]){
			add_edge(a[i],b[i],c[lab]-c[i]+1);
			add_edge(b[i],a[i],c[lab]-c[i]+1);
		}
	}
	
	printf("%d",dinic());
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值