重修 网络最大流

基础

本蒟蒻只讲一些我常常出错的部分,具体还要靠 OI-wiki:Dinic 的帮助。

啥是最大流?

相当于有一个供水站,一个用户,中间有复杂的水管(每一根单向且有单位时间传输量限制)网络,求用户单位时间内获得的最大水量。

Dinic 咋弄?

每次先 BFS 按照离原点 S 的距离将图分层,再在分层图上 DFS,终止条件为 BFS 时候汇点 TS 不连通。DFS 时每次找一条通的管道注水,当然一次 DFS 整体看起来像打通了一棵树(多路增广)。

如果一条边已经被增广过,那么它就没有可能被增广第二次。那么,我们下一次进行增广的时候,就可以不必再走那些已经被增广过的边(当前弧优化)。

时间?

O(n2m)(前提是加上多路增广和当前弧优化)(事实上在一般的网络上,Dinic 算法往往达不到这个上界。)

特别地,在求解二分图最大匹配问题时,Dinic 算法的时间复杂度是 O(mn)

建议?

建议一遍写对,难调的很 qwq。

我的代码?

Luogu 模板题:

点击查看代码
//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到达,这条边就在最小割里

证明:

  1. 不能到出点,所以这些边一定都满流

  2. 由于一定不在同一条路径上,所以之和一定是最大流

  3. 找出的边一定是割集,否则有增广路还可以增加最大流

转载自 Miracle 的blog

退流

解决啥问题

跑完网络流之后,要减边,然后问你最大流(最大费用)。

咋做

设要删掉的边为 uv。我们从 uS 跑最大流(退流),再从 Tv 跑最大流。最后将 uv 及其反向边 flow 归零(删除)即可。

例题

P3308 [SDOI2014]LIS

请看代码↓

点击查看代码
//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;}
posted @   ShaoJia  阅读(42)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
点击右上角即可分享
微信分享提示