重修 网络最大流
基础
本蒟蒻只讲一些我常常出错的部分,具体还要靠 OI-wiki:Dinic 的帮助。
啥是最大流?
相当于有一个供水站,一个用户,中间有复杂的水管(每一根单向且有单位时间传输量限制)网络,求用户单位时间内获得的最大水量。
Dinic 咋弄?
每次先 BFS 按照离原点 \(S\) 的距离将图分层,再在分层图上 DFS,终止条件为 BFS 时候汇点 \(T\) 与 \(S\) 不连通。DFS 时每次找一条通的管道注水,当然一次 DFS 整体看起来像打通了一棵树(多路增广)。
如果一条边已经被增广过,那么它就没有可能被增广第二次。那么,我们下一次进行增广的时候,就可以不必再走那些已经被增广过的边(当前弧优化)。
时间?
\(O(n^2m)\)(前提是加上多路增广和当前弧优化)(事实上在一般的网络上,Dinic 算法往往达不到这个上界。)
特别地,在求解二分图最大匹配问题时,Dinic 算法的时间复杂度是 \(O(m\sqrt{n})\)。

建议?
建议一遍写对,难调的很 qwq。
我的代码?
点击查看代码
//Said no more counting dollars. We'll be counting stars.
//#pragma GCC optimize("Ofast")
//#pragma GCC optimize("unroll-loops")//DONT use rashly,I have suffered
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,avx2,tune=native")//DONT use rashly,I have suffered
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define fir first
#define sec second
#define mkp make_pair
#define pb emplace_back
#define mem(x,y) memset(x,y,sizeof(x))
#define For(i,j,k) for(int i=j;i<=k;i++)
#define Rof(i,j,k) for(int i=j;i>=k;i--)
#define Fe(x,y) for(int x=head[y];x;x=e[x].nxt)
#define ckmx(a,b) a=max(a,b)
#define ckmn(a,b) a=min(a,b)
#define fin(s) freopen(s,"r",stdin)
#define fout(s) freopen(s,"w",stdout)
#define file(s) fin(s".in");fout(s".out")
#define cerr cerr<<'_'
#define debug cerr<<"Passed line #"<<__LINE__<<endl
template<typename T>T ov(T x){cerr<<"Value: "<<x<<endl;return x;}
#define ll long long
const ll mod=1000000007;
inline ll pw(ll x,ll y){ll r=1;while(y){if(y&1)r=r*x%mod;x=x*x%mod;y>>=1;}return r;}
inline void mad(ll &a,ll b){a=(a+b)%mod;while(a<0)a+=mod;}
inline void mmu(ll &a,ll b){a=a*b%mod;while(a<0)a+=mod;}
#define inv(a) pw(a,mod-2)
#define int long long
#define N 202
#define M 5002
const int inf=1e17;
struct edge{int nxt,to,flow;}e[2*M];//反向边空间开两倍!!! 
int n,m,S,T,tot=1,head[N],cur[N],dis[N];
void adde(int x,int y,int z){
	e[++tot]=(edge){head[x],y,z}; head[x]=tot;
	e[++tot]=(edge){head[y],x,0}; head[y]=tot;
}
queue<int> q; 
bool bfs(){
	For(i,1,n) dis[i]=0;
	dis[S]=1;
	q.push(S);
	int x;
	while(!q.empty()){
		x=q.front();
		q.pop();
		cur[x]=head[x];
		for(int i=head[x],to;i;i=e[i].nxt){
			to=e[i].to;
			if(!e[i].flow || dis[to]) continue;
			dis[to]=dis[x]+1;
			q.push(to);
		}
	}
	return dis[T];
}
int dfs(int x,int flow){
	if(x==T) return flow;
	int res=0,tmp,to;
	for(int& i=cur[x];i;i=e[i].nxt){
		to=e[i].to;
		if(!e[i].flow || dis[to]!=dis[x]+1) continue;
		tmp=dfs(to,min(flow,e[i].flow));
		e[i].flow-=tmp;
		e[i^1].flow+=tmp;
		flow-=tmp;
		res+=tmp;
		if(!flow) break;
	}
	return res;
}
signed main(){IOS;
	cin>>n>>m>>S>>T;
	int x,y,z;
	For(i,1,m){
		cin>>x>>y>>z;
		adde(x,y,z);
	} 
	int ans=0;
	while(bfs()) ans+=dfs(S,inf);
	cout<<ans<<endl;
return 0;}
最小点割(要拆点)原题:
点击查看代码
//Said no more counting dollars. We'll be counting stars.
#pragma GCC optimize(2,3)//For Web Contests
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define pb emplace_back
#define For(i,j,k) for(int i=j;i<=k;i++)
#define Rof(i,j,k) for(int i=j;i>=k;i--)
#define int long long
#define N 220
#define endl '\n'
const int inf=1e12;
struct node{int to,c,nxt;}e[N*N];
int head[N],cur[N],tot=1,n,m,s,t,dep[N],col[N];
inline void adde(int x,int y,int c){
	e[++tot]={y,c,head[x]};head[x]=tot;
	e[++tot]={x,0,head[y]};head[y]=tot;
}
queue<int> q;
bool bfs(){
	For(i,1,2*n+2) dep[i]=0;
	dep[s]=1;
	while(!q.empty()) q.pop();
	q.push(s);
	int x;
	while(!q.empty()){
		x=q.front();
		cur[x]=head[x];
		q.pop();
		for(int i=head[x];i;i=e[i].nxt){
			if(!e[i].c || dep[e[i].to]) continue;
			dep[e[i].to]=dep[x]+1;
			q.push(e[i].to);
		}
	}
	return dep[t];
}
int dfs(int rt,int flow){
	if(rt==t) return flow;
	int res=0,tmp;
	for(int &i=cur[rt];i;i=e[i].nxt){//当前弧优化&多路增广 
		if(!e[i].c || dep[e[i].to]!=1+dep[rt]) continue;
		tmp=dfs(e[i].to,min(flow,e[i].c));
		e[i].c-=tmp;
		e[i^1].c+=tmp;
		flow-=tmp;
		res+=tmp;
		if(!flow) break;
	}
	return res;
}
int dinic(){
	int res=0;
	while(bfs()) res+=dfs(s,inf);
	return res;
}
void color(int rt){
	col[rt]=1;
	for(int i=head[rt];i;i=e[i].nxt){
		if(!e[i].c || col[e[i].to]) continue;
		color(e[i].to);
	}
}
signed main(){IOS;
	cin>>n>>m;
	int x,y;
	while(m--){
		cin>>x>>y;
		adde(x,y+n,inf);
		adde(y,x+n,inf);
	} 
	For(i,1,n){
		cin>>x;
		if(i==1 || i==n) x=inf;
		adde(i+n,i,x);
	}
	s=n*2+1,t=n*2+2;
	adde(s,1,inf);
	adde(n*2,t,inf);
	cout<<dinic()<<endl;
	color(s);
	vector<int> ans;
	For(i,2,n-1)
		if(col[i]!=col[i+n])
			ans.pb(i);
	cout<<ans.size()<<endl;
	for(int i:ans) cout<<i<<" "; cout<<endl;
return 0;}
最小割的可行边与必须边
就是在残量网络上跑tarjan
可行边:
满流并且残量网络上不能存在入点到出点的路径
必须边:
满流并且残量网络上入点能从源点到达,出点能到汇点。
任意一种最小割求法:
跑一边最大流
残量网络上从S开始BFS,标记能到达的点
如果一个边的入点能从S到达,出点不能从S到达,这条边就在最小割里
证明:
- 
不能到出点,所以这些边一定都满流
 - 
由于一定不在同一条路径上,所以之和一定是最大流
 - 
找出的边一定是割集,否则有增广路还可以增加最大流
 
转载自 Miracle 的blog
退流
解决啥问题
跑完网络流之后,要减边,然后问你最大流(最大费用)。
咋做
设要删掉的边为 \(u\to v\)。我们从 \(u\) 到 \(S\) 跑最大流(退流),再从 \(T\) 到 \(v\) 跑最大流。最后将 \(u\to v\) 及其反向边 flow 归零(删除)即可。
例题
请看代码↓
点击查看代码
//Said no more counting dollars. We'll be counting stars.
#include<bits/stdc++.h>
using namespace std;
#define For(i,j,k) for(int i=j;i<=k;i++)
#define Rof(i,j,k) for(int i=j;i>=k;i--)
#define ckmx(a,b) a=max(a,b)
#define ckmn(a,b) a=min(a,b)
#define int long long
#define N 1410
#define M 247200
#define endl '\n'
struct edge{int nxt,to,flow;}e[2*M];
const int inf=1e9;
int n,a[N],b[N],c[N],f[N],L,S,T,tot,head[N],cur[N],dis[N],all;
void adde(int x,int y,int z){
	e[++tot]=(edge){head[x],y,z};head[x]=tot;
	e[++tot]=(edge){head[y],x,0};head[y]=tot;
}
struct node{
	int num,id,w;
	friend bool operator<(node x,node y){return x.w<y.w;}
}g[N];
queue<int> q;
bool check(int S,int T){//其实就是弱化版的 bfs(),bfs() 当 check() 太慢过不去 
 	For(i,1,all) dis[i]=0;
	dis[S]=1;
	q.push(S);
	int x;
	while(!q.empty()){
		x=q.front();
		q.pop();
		for(int i=head[x],to;i;i=e[i].nxt){
			to=e[i].to;
			if(!e[i].flow || dis[to]) continue;
			if(to==T){
				while(!q.empty()) q.pop();//记得清空 
				return true;
			}
			dis[to]=1;
			q.push(to);
		}
	}
	return false;
}
bool bfs(int S,int T){
	For(i,1,all) dis[i]=0;
	dis[S]=1;
	q.push(S);
	int x;
	while(!q.empty()){
		x=q.front();
		q.pop();
		cur[x]=head[x];
		for(int i=head[x],to;i;i=e[i].nxt){
			to=e[i].to;
			if(!e[i].flow || dis[to]) continue;
			dis[to]=dis[x]+1;
			q.push(to);
		}
	}
	return dis[T];
}
int dfs(int x,int T,int flow){
	if(x==T) return flow;
	int res=0,tmp,to;
	for(int &i=cur[x];i;i=e[i].nxt){
		to=e[i].to;
		if(dis[to]!=dis[x]+1 || !e[i].flow) continue;
		tmp=dfs(to,T,min(flow,e[i].flow));
		e[i].flow-=tmp;
		e[i^1].flow+=tmp;
		res+=tmp;
		flow-=tmp;
		if(!flow) break;
	}
	return res;
} 
int Dinic(int S,int T){
	int res=0;
	while(bfs(S,T)) res+=dfs(S,T,inf);
	return res; 
}
int ans,out[N],oc;
void work(){
	scanf("%lld",&n);
	For(i,1,n) scanf("%lld",a+i);
	For(i,1,n) scanf("%lld",b+i);
	For(i,1,n) scanf("%lld",c+i);
	int S=n*2+1;T=S+1;
	all=T;tot=1;
	For(i,1,all) head[i]=0;
	For(i,1,n){
		f[i]=1;
		For(j,1,i-1) if(a[j]<a[i]) ckmx(f[i],f[j]+1);
	}
	L=f[1];
	For(i,2,n) ckmx(L,f[i]);
	For(i,1,n){
		if(f[i]==1) adde(S,i,inf);
		if(f[i]==L) adde(i+n,T,inf);
	} 
	For(i,2,n) For(j,1,i-1) if(f[i]==f[j]+1) adde(j+n,i,inf);
	For(i,1,n){
		adde(i,i+n,b[i]);
		g[i]=(node){i,tot-1,c[i]};//id 为正向边边权 
	}
	sort(g+1,g+1+n);//按照附加属性排 
	ans=Dinic(S,T);
	oc=0;
	int x;
	For(i,1,n){
		x=g[i].num;
		if(check(x,x+n)) continue;//废了,不是可行割
		out[++oc]=x;
		Dinic(x,S);//退流 
		Dinic(T,x+n);//这两步骤后流平衡 
		e[g[i].id].flow=e[g[i].id^1].flow=0;//删边 
	} 
	printf("%lld %lld\n",ans,oc);
	sort(out+1,out+1+oc); 
	For(i,1,oc) printf("%lld ",out[i]); puts("");
}
signed main(){
	int C;scanf("%lld",&C);
	while(C--)work();
return 0;}
作者:ShaoJia,欢迎分享本文,转载时敬请注明原文来源链接。

                
            
        
浙公网安备 33010602011771号