重修 网络最大流
基础
本蒟蒻只讲一些我常常出错的部分,具体还要靠 OI-wiki:Dinic 的帮助。
啥是最大流?
相当于有一个供水站,一个用户,中间有复杂的水管(每一根单向且有单位时间传输量限制)网络,求用户单位时间内获得的最大水量。
Dinic 咋弄?
每次先 BFS 按照离原点 的距离将图分层,再在分层图上 DFS,终止条件为 BFS 时候汇点 与 不连通。DFS 时每次找一条通的管道注水,当然一次 DFS 整体看起来像打通了一棵树(多路增广)。
如果一条边已经被增广过,那么它就没有可能被增广第二次。那么,我们下一次进行增广的时候,就可以不必再走那些已经被增广过的边(当前弧优化)。
时间?
(前提是加上多路增广和当前弧优化)(事实上在一般的网络上,Dinic 算法往往达不到这个上界。)
特别地,在求解二分图最大匹配问题时,Dinic 算法的时间复杂度是 。
建议?
建议一遍写对,难调的很 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
退流
解决啥问题
跑完网络流之后,要减边,然后问你最大流(最大费用)。
咋做
设要删掉的边为 。我们从 到 跑最大流(退流),再从 到 跑最大流。最后将 及其反向边 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,版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义