概念
最大流,网络流中的一个问题。
网络流即指一个源点 s s s有无尽的水源,经过一些最多流 c i c_i ci的水的管道,到达汇点 t t t的图。
流量:经过某个管道的水的总量或者到达某个点的水的总量。
容量:即指上面所述的 c i c_i ci,管道最大能流的流量。
一个可行流就是满足以下两点的一个流:
- 流量守恒:流到点u多少就从点u流出去多少。(源点和汇点除外)
- 容量限制:每一条边的流量不大于该边的容量。
最大流,故名思意,就是可行流中到达 t t t(从 s s s流出去)的流量最大的一个。
退流:把流过的流量又流回去,相当于“退货”。
残留网络:指加上可以退流的边的图。退流的边就是反向边。反向边的容量等于原来的边已用的容量,原来的边就是总容量减去用去的容量。反向边为的是给一个后悔的机会,正向边方便计算增广路。
增广路:指能增加最大流量的一条水流的路径。
分层图:把图分成很多层的图。层数就是到 s s s的最短距离。
算法
EK算法
方法
每次找一条增广路,答案该增广路的流量,并修改残留网络,原边减去增广路的流量,反向边加上增广路的流量。
时间复杂度 O ( n m 2 ) O(nm^2) O(nm2)。
例题
AcWing 2171
板子题,直接把模板贴上去即可
#include<bits/stdc++.h>
using namespace std;
const int NN=1004,MM=20004;
int n,m,s,t,w[MM],e[MM],head[NN],ne[MM],d[NN],pre[NN],idx=-1;
bool st[NN];
void add(int u,int v,int c)
{
e[++idx]=v;
ne[idx]=head[u];
w[idx]=c;
head[u]=idx;
e[++idx]=u;
ne[idx]=head[v];
head[v]=idx;
}
bool bfs()
{
queue<int>q;
q.push(s);
memset(st,false,sizeof(st));
st[s]=true;
d[s]=1e9;
while(q.size())
{
int f=q.front();
q.pop();
for(int i=head[f];~i;i=ne[i])
{
int v=e[i];
if(!st[v]&&w[i])
{
st[v]=true;
d[v]=min(d[f],w[i]);
pre[v]=i;
if(v==t)
return true;
q.push(v);
}
}
}
return false;
}
int EK()
{
int res=0;
while(bfs())
{
res+=d[t];
for(int i=t;i!=s;i=e[pre[i]^1])
{
w[pre[i]]-=d[t];
w[pre[i]^1]+=d[t];
}
}
return res;
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=m;i++)
{
int u,v,c;
scanf("%d%d%d",&u,&v,&c);
add(u,v,c);
}
printf("%d",EK());
return 0;
}
dinic算法
方法
其实是 E K EK EK算法的一种优化,每次取完能取的一切增广路。因为图中有可能有环导致死循环,则建一个分层图,只能一层一层地走,就不会往回走而死循环了。考虑如何取完所有能取的增广路,设定一个从源点流向 u u u的最大流量,然后 u u u将流量分给所有的 v v v。注意分给 v v v的流量不能超过 u → v u\to v u→v的边的容量。
时间复杂度 O ( n 2 m ) O(n^2m) O(n2m)。
优化
d i n i c dinic dinic算法仍然可以优化。
- 当前弧优化:每次取完了前面的一些路径的流量,则以后没有必要再搜索了。加了该优化必须加优化 2 2 2,否则新的邻接表的表头就会一直被更新,容易 T L E TLE TLE。
- 可行性优化:如果给 u u u分的流量等于目前 u u u分给所有儿子的流量,那么再枚举也没有必要了。因为前面分给 u u u的流量已经全部分完了。
- 重复优化:如果之前算过某个路径不行,那么以后在该分层图上就不用计算该路径了。
例题
AcWing 2172
板子题,直接套板子
#include<bits/stdc++.h>
using namespace std;
const int NN=10004,MM=200004;
int n,m,s,t,h[NN],ne[MM],e[MM],c[MM],idx=-1,head[NN],d[NN];
void add(int u,int v,int w)
{
e[++idx]=v;
c[idx]=w;
ne[idx]=h[u];
h[u]=idx;
e[++idx]=u;
ne[idx]=h[v];
h[v]=idx;
}
bool bfs()
{
queue<int>q;
memset(d,-1,sizeof(d));
q.push(s);
d[s]=0;
head[s]=h[s];
while(q.size())
{
int f=q.front();
q.pop();
for(int i=h[f];~i;i=ne[i])
{
int v=e[i];
if(d[v]==-1&&c[i])
{
d[v]=d[f]+1;
head[v]=h[v];
if(v==t)
return true;
q.push(v);
}
}
}
return false;
}
int find(int u,int sum)
{
if(u==t)
return sum;
int res=0;
for(int i=head[u];~i&&res<sum;i=ne[i])
{
head[u]=i;
int v=e[i];
if(d[v]==d[u]+1&&c[i])
{
int temp=find(v,min(c[i],sum-res));
if(!temp)
d[v]=-1;
c[i]-=temp;
c[i^1]+=temp;
res+=temp;
}
}
return res;
}
int dinic()
{
int res=0,sum;
while(bfs())
while(sum=find(s,1e9))
res+=sum;
return res;
}
int main()
{
scanf("%d%d%d%d",&n,&m,&s,&t);
memset(h,-1,sizeof(h));
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
}
printf("%d",dinic());
return 0;
}
最大流解二分图
概念
二分图就是把无向图的点集分成两个部分,要求每条边都跨过两个点集的一个图。这里会介绍如何用网络流解决二分图问题。
方法
其实就是左边的点集的每一个点,可以选择一个右边没有被选择过的点,或者不选。相当于给左边一个流量,让它选择流到右边哪个点。到了右边,因为不能有多个左边的点选择右边的同一个点,所以相当于每个右边的点只能选一个左边的点帮你把水流到终点,则向 t t t连一条容量为 1 1 1的边。
例题
AcWing 2175
就是二分图,但是匈牙利算法时间不够,网络流可以更快。题目要求方案,本题中横跨左右且流了水的边,就说明匹配了该边连接的左右两个点。本题中流了水的边就是残留网络中正向边容量为 0 0 0或者反向边容量为 1 1 1的边。
#include<bits/stdc++.h>
using namespace std;
const int NN=10004,MM=200004;
int s,t,h[NN],ne[MM],e[MM],c[MM],idx=-1,head[NN],d[NN];
void add(int u,int v,int w)
{
e[++idx]=v;
c[idx]=w;
ne[idx]=h[u];
h[u]=idx;
e[++idx]=u;
ne[idx]=h[v];
h[v]=idx;
}
bool bfs()
{
queue<int>q;
memset(d,-1,sizeof(d));
q.push(s);
d[s]=0;
head[s]=h[s];
while(q.size())
{
int f=q.front();
q.pop();
for(int i=h[f];~i;i=ne[i])
{
int v=e[i];
if(d[v]==-1&&c[i])
{
d[v]=d[f]+1;
head[v]=h[v];
if(v==t)
return true;
q.push(v);
}
}
}
return false;
}
int find(int u,int sum)
{
if(u==t)
return sum;
int res=0;
for(int i=head[u];~i&&res<sum;i=ne[i])
{
head[u]=i;
int v=e[i];
if(d[v]==d[u]+1&&c[i])
{
int temp=find(v,min(c[i],sum-res));
if(!temp)
d[v]=-1;
c[i]-=temp;
c[i^1]+=temp;
res+=temp;
}
}
return res;
}
int dinic()
{
int res=0,sum;
while(bfs())
while(sum=find(s,1e9))
res+=sum;
return res;
}
int main()
{
int m,n;
scanf("%d%d",&m,&n);
t=n+1;
memset(h,-1,sizeof(h));
for(int i=1;i<=m;i++)
add(s,i,1);
for(int i=m+1;i<=n;i++)
add(i,t,1);
int u,v;
while(scanf("%d%d",&u,&v)!=EOF)
add(u,v,1);
printf("%d\n",dinic());
for(int i=0;i<idx;i+=2)
if(e[i]>m&&e[i]<=n&&!c[i])
printf("%d %d\n",e[i^1],e[i]);
return 0;
}
AcWing 2179
这个题就相当于左边和右边的点可以使用多次,则把 s s s到左边的边和右边到 t t t的边的容量限制放开就行了。一个单位不在同一个桌,那么左边的点就只能向右边某一个点流一个。
#include<bits/stdc++.h>
using namespace std;
const int NN=1004,MM=2000004;
int s,t,h[NN],ne[MM],e[MM],c[MM],idx=-1,head[NN],d[NN];
void add(int u,int v,int w)
{
e[++idx]=v;
c[idx]=w;
ne[idx]=h[u];
h[u]=idx;
e[++idx]=u;
ne[idx]=h[v];
h[v]=idx;
}
bool bfs()
{
queue<int>q;
memset(d,-1,sizeof(d));
q.push(s);
d[s]=0;
head[s]=h[s];
while(q.size())
{
int f=q.front();
q.pop();
for(int i=h[f];~i;i=ne[i])
{
int v=e[i];
if(d[v]==-1&&c[i])
{
d[v]=d[f]+1;
head[v]=h[v];
if(v==t)
return true;
q.push(v);
}
}
}
return false;
}
int find(int u,int sum)
{
if(u==t)
return sum;
int res=0;
for(int i=head[u];~i&&res<sum;i=ne[i])
{
head[u]=i;
int v=e[i];
if(d[v]==d[u]+1&&c[i])
{
int temp=find(v,min(c[i],sum-res));
if(!temp)
d[v]=-1;
c[i]-=temp;
c[i^1]+=temp;
res+=temp;
}
}
return res;
}
int dinic()
{
int res=0,sum;
while(bfs())
while(sum=find(s,1e9))
res+=sum;
return res;
}
int main()
{
int m,n;
scanf("%d%d",&m,&n);
t=m+n+1;
memset(h,-1,sizeof(h));
int sum=0;
for(int i=1;i<=m;i++)
{
int res;
scanf("%d",&res);
add(s,i,res);
sum+=res;
}
for(int i=1;i<=n;i++)
{
int res;
scanf("%d",&res);
add(i+m,t,res);
}
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
add(i,j+m,1);
if(dinic()!=sum)
puts("0");
else
{
puts("1");
for(int i=1;i<=m;i++)
{
for(int j=h[i];~j;j=ne[j])
if(e[j]>m&&e[j]<=n+m&&!c[j])
printf("%d ",e[j]-m);
puts("");
}
}
return 0;
}
上下界可行流
概念
图中每条管道加上了容量下界。
方法
上界减去下界,最后每一条边的流量加上下界的流量就是答案。但是这样 G ′ G' G′流量守恒,但有可能原图 G G G流量不守恒。那么如果原图流入的多,就从 s s s向该点补充原图到新图少了的流量。反之向 t t t把多了的流出去。只有满流(这样才流量守恒)才有解。
例题
AcWing 2188
这个题按刚才说的方法做就行了。
#include<bits/stdc++.h>
using namespace std;
const int NN=204,MM=20804;
int h[NN],ne[MM],e[MM],c[MM],d[NN],head[NN],x[NN],down[MM],idx=-1,s,t;
void add(int u,int v,int l,int w)
{
e[++idx]=v;
ne[idx]=h[u];
c[idx]=w;
down[idx]=l;
h[u]=idx;
e[++idx]=u;
ne[idx]=h[v];
h[v]=idx;
}
bool bfs()
{
queue<int>q;
memset(d,-1,sizeof(d));
d[s]=0;
q.push(s);
head[s]=h[s];
while(q.size())
{
int f=q.front();
q.pop();
for(int i=h[f];~i;i=ne[i])
{
int v=e[i];
if(d[v]==-1&&c[i])
{
d[v]=d[f]+1;
head[v]=h[v];
if(v==t)
return true;
q.push(v);
}
}
}
return false;
}
int find(int u,int minn)
{
if(u==t)
return minn;
int res=0;
for(int i=head[u];~i&&res<minn;i=ne[i])
{
head[u]=i;
int v=e[i];
if(d[v]==d[u]+1&&c[i])
{
int temp=find(v,min(c[i],minn-res));
if(!temp)
d[v]=-1;
c[i]-=temp;
c[i^1]+=temp;
res+=temp;
}
}
return res;
}
int dinic()
{
int res=0,sum;
while(bfs())
while(sum=find(s,1e9))
res+=sum;
return res;
}
int main()
{
int n,m,res=0;
scanf("%d%d",&n,&m);
t=n+1;
memset(h,-1,sizeof(h));
for(int i=1;i<=m;i++)
{
int u,v,a,b;
scanf("%d%d%d%d",&u,&v,&a,&b);
add(u,v,a,b-a);
x[u]-=a;
x[v]+=a;
}
for(int i=1;i<=n;i++)
if(x[i]>0)
{
add(s,i,0,x[i]);
res+=x[i];
}
else if(x[i]<0)
add(i,t,0,-x[i]);
if(dinic()!=res)
printf("NO");
else
{
puts("YES");
for(int i=0;i<m*2;i+=2)
printf("%d\n",c[i^1]+down[i]);
}
return 0;
}
AcWing 2189
这个题有源汇,那么可以先把 G ′ G' G′算出来,把不守恒的流量补齐了再计算有源汇的图。但是原图 G G G有源点和汇点,但是它们变成新图就不能无限出和无限入了。保证变成无源汇之后流量守恒,那么就得让 t t t能无限出, s s s能无限入,则连一条 t t t到 s s s的边就行了。如果是满流,这时就补充了缺失的流量,保证了流量守恒。再把 s s s变成 S S S, t t t变成 T T T,把原图 t t t到 s s s的边删除(因为这条边是为了流量守恒的)即可。两个图的流量相加就是答案。
#include<bits/stdc++.h>
using namespace std;
const int NN=204,MM=20804;
int h[NN],ne[MM],e[MM],c[MM],d[NN],head[NN],x[NN],idx=-1,S,T;
void add(int u,int v,int w)
{
e[++idx]=v;
ne[idx]=h[u];
c[idx]=w;
h[u]=idx;
e[++idx]=u;
ne[idx]=h[v];
h[v]=idx;
}
bool bfs()
{
queue<int>q;
memset(d,-1,sizeof(d));
d[S]=0;
q.push(S);
head[S]=h[S];
while(q.size())
{
int f=q.front();
q.pop();
for(int i=h[f];~i;i=ne[i])
{
int v=e[i];
if(d[v]==-1&&c[i])
{
d[v]=d[f]+1;
head[v]=h[v];
if(v==T)
return true;
q.push(v);
}
}
}
return false;
}
int find(int u,int minn)
{
if(u==T)
return minn;
int res=0;
for(int i=head[u];~i&&res<minn;i=ne[i])
{
head[u]=i;
int v=e[i];
if(d[v]==d[u]+1&&c[i])
{
int temp=find(v,min(c[i],minn-res));
if(!temp)
d[v]=-1;
c[i]-=temp;
c[i^1]+=temp;
res+=temp;
}
}
return res;
}
int dinic()
{
int res=0,sum;
while(bfs())
while(sum=find(S,1e9))
res+=sum;
return res;
}
int main()
{
int n,m,s,t,res=0;
scanf("%d%d%d%d",&n,&m,&s,&t);
T=n+1;
memset(h,-1,sizeof(h));
for(int i=1;i<=m;i++)
{
int u,v,a,b;
scanf("%d%d%d%d",&u,&v,&a,&b);
add(u,v,b-a);
x[u]-=a;
x[v]+=a;
}
for(int i=1;i<=n;i++)
if(x[i]>0)
{
add(S,i,x[i]);
res+=x[i];
}
else if(x[i]<0)
add(i,T,-x[i]);
add(t,s,1e9);
if(dinic()!=res)
printf("No Solution");
else
{
int res=c[idx];
c[idx]=c[idx-1]=0;
S=s;
T=t;
printf("%d",res+dinic());
}
return 0;
}
AcWing 2190
这个题是求最小流,那么从跑完 G ′ G' G′后,跑 G ′ ′ G'' G′′时可以跑退流的边,把前面补完流量的最大流多补的退回去。退得越多原图 G G G流量就越小,所以可以在退流的边上跑最大流。最后原图的流量减去退回去的流量就是答案
#include<bits/stdc++.h>
using namespace std;
const int NN=50007,MM=(NN+125003)*2;
int h[NN],ne[MM],e[MM],c[MM],d[NN],head[NN],x[NN],idx=-1,S,T;
void add(int u,int v,int w)
{
e[++idx]=v;
ne[idx]=h[u];
c[idx]=w;
h[u]=idx;
e[++idx]=u;
ne[idx]=h[v];
h[v]=idx;
}
bool bfs()
{
queue<int>q;
memset(d,-1,sizeof(d));
d[S]=0;
q.push(S);
head[S]=h[S];
while(q.size())
{
int f=q.front();
q.pop();
for(int i=h[f];~i;i=ne[i])
{
int v=e[i];
if(d[v]==-1&&c[i])
{
d[v]=d[f]+1;
head[v]=h[v];
if(v==T)
return true;
q.push(v);
}
}
}
return false;
}
int find(int u,int minn)
{
if(u==T)
return minn;
int res=0;
for(int i=head[u];~i&&res<minn;i=ne[i])
{
head[u]=i;
int v=e[i];
if(d[v]==d[u]+1&&c[i])
{
int temp=find(v,min(c[i],minn-res));
if(!temp)
d[v]=-1;
c[i]-=temp;
c[i^1]+=temp;
res+=temp;
}
}
return res;
}
int dinic()
{
int res=0,sum;
while(bfs())
while(sum=find(S,1e9))
res+=sum;
return res;
}
int main()
{
int n,m,s,t,res=0;
scanf("%d%d%d%d",&n,&m,&s,&t);
T=n+1;
memset(h,-1,sizeof(h));
for(int i=1;i<=m;i++)
{
int u,v,a,b;
scanf("%d%d%d%d",&u,&v,&a,&b);
add(u,v,b-a);
x[u]-=a;
x[v]+=a;
}
for(int i=1;i<=n;i++)
if(x[i]>0)
{
add(S,i,x[i]);
res+=x[i];
}
else if(x[i]<0)
add(i,T,-x[i]);
add(t,s,1e9);
if(dinic()!=res)
printf("No Solution");
else
{
int res=c[idx];
c[idx]=c[idx-1]=0;
S=t;
T=s;
printf("%d",res-dinic());
}
return 0;
}
多源汇最大流
概念
本模型会有多个源点和汇点。
方法
从s向每一个源点供给无限多,每个汇点可以向t流无限多流量即可。
例题
AcWing 2234
这个题按上面的方法跑一遍就能过
#include<bits/stdc++.h>
using namespace std;
const int NN=10004,MM=(1e5+NN)*2,INF=1e9;
int e[MM],ne[MM],c[MM],h[NN],idx=-1,head[NN],d[NN],S,T;
void add(int u,int v,int w)
{
e[++idx]=v;
ne[idx]=h[u];
c[idx]=w;
h[u]=idx;
e[++idx]=u;
ne[idx]=h[v];
h[v]=idx;
}
bool bfs()
{
queue<int>q;
memset(d,-1,sizeof(d));
q.push(S);
d[S]=0;
head[S]=h[S];
while(q.size())
{
int f=q.front();
q.pop();
for(int i=h[f];~i;i=ne[i])
{
int v=e[i];
if(d[v]==-1&&c[i])
{
d[v]=d[f]+1;
head[v]=h[v];
if(v==T)
return true;
q.push(v);
}
}
}
return false;
}
int find(int u,int sum)
{
if(u==T)
return sum;
int res=0;
for(int i=head[u];~i&&res<sum;i=ne[i])
{
int v=e[i];
head[u]=i;
if(d[v]==d[u]+1&&c[i])
{
int t=find(v,min(c[i],sum-res));
if(!t)
d[v]=-1;
c[i]-=t;
c[i^1]+=t;
res+=t;
}
}
return res;
}
int dinic()
{
int res=0,sum;
while(bfs())
while(sum=find(S,INF))
res+=sum;
return res;
}
int main()
{
int n,m,s,t;
scanf("%d%d%d%d",&n,&m,&s,&t);
T=n+1;
memset(h,-1,sizeof(h));
for(int i=1;i<=s;i++)
{
int x;
scanf("%d",&x);
add(S,x,INF);
}
for(int i=1;i<=t;i++)
{
int x;
scanf("%d",&x);
add(x,T,INF);
}
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
}
printf("%d",dinic());
return 0;
}
技巧:拆点
方法
假如某一个点只能用 n n n次或者有多种状态,那么就可以拆点解决了。如果只能用一次,那就把一个点拆成入点和出点,所有进来的边都连向入点,出去的边从出点连,入点向出点连一条容量为 n n n的边。多种状态的就按题目要求拆点。
例题
AcWing 2240
可以把奶牛放在中间,向食物和饮料连边。但是一个奶牛只能吃一套,所以把表示奶牛的点拆点解决。
#include<bits/stdc++.h>
using namespace std;
const int NN=404,MM=40604;
int h[NN],ne[MM],e[MM],c[MM],d[NN],head[NN],x[NN],idx=-1,s,t;
void add(int u,int v,int w)
{
e[++idx]=v;
ne[idx]=h[u];
c[idx]=w;
h[u]=idx;
e[++idx]=u;
ne[idx]=h[v];
h[v]=idx;
}
bool bfs()
{
queue<int>q;
memset(d,-1,sizeof(d));
d[s]=0;
q.push(s);
head[s]=h[s];
while(q.size())
{
int f=q.front();
q.pop();
for(int i=h[f];~i;i=ne[i])
{
int v=e[i];
if(d[v]==-1&&c[i])
{
d[v]=d[f]+1;
head[v]=h[v];
if(v==t)
return true;
q.push(v);
}
}
}
return false;
}
int find(int u,int minn)
{
if(u==t)
return minn;
int res=0;
for(int i=head[u];~i&&res<minn;i=ne[i])
{
head[u]=i;
int v=e[i];
if(d[v]==d[u]+1&&c[i])
{
int temp=find(v,min(c[i],minn-res));
if(!temp)
d[v]=-1;
c[i]-=temp;
c[i^1]+=temp;
res+=temp;
}
}
return res;
}
int dinic()
{
int res=0,sum;
while(bfs())
while(sum=find(s,1e9))
res+=sum;
return res;
}
int main()
{
int n,f,d;
scanf("%d%d%d",&n,&f,&d);
t=n*2+f+d+1;
memset(h,-1,sizeof(h));
for(int i=1;i<=f;i++)
add(s,n*2+i,1);
for(int i=1;i<=d;i++)
add(n*2+f+i,t,1);
for(int i=1;i<=n;i++)
{
add(i,n+i,1);
int a,b;
scanf("%d%d",&a,&b);
while(a--)
{
int x;
scanf("%d",&x);
add(n*2+x,i,1);
}
while(b--)
{
int x;
scanf("%d",&x);
add(i+n,n*2+f+x,1);
}
}
printf("%d",dinic());
return 0;
}
AcWing 2180
这个题第一问就是一个动态规划。第二问每个数可以从多个数转移过来且都能得到最大值,只要满足 f [ u ] = f [ v ] + 1 f[u]=f[v]+1 f[u]=f[v]+1且 a [ v ] < a [ u ] a[v]<a[u] a[v]<a[u]即可,把 v v v向 u u u连一条容量为 1 1 1的边,相当于让 u u u选择一个最优策略组合在一起。每个点只能用一次,拆点。所有 f [ u ] f[u] f[u]等于最优值的向t连一条容量为 1 1 1的边,因为它们会做一条链的终点。所有 f [ u ] f[u] f[u]等于 1 1 1的由 s s s连一条容量为 1 1 1的边,因为它们会作为一条链的起点。第三问,把 s s s到 1 1 1, 1 1 1到 t t t, s s s到 n n n, n n n到 t t t和这两个数拆的点之间边的容量放开即可。
#include<bits/stdc++.h>
using namespace std;
const int NN=1004,MM=251004;
int h[NN],ne[MM],e[MM],c[MM],d[NN],head[NN],l[NN],g[NN],idx=-1,s,t;
void add(int u,int v,int w)
{
e[++idx]=v;
ne[idx]=h[u];
c[idx]=w;
h[u]=idx;
e[++idx]=u;
ne[idx]=h[v];
h[v]=idx;
}
bool bfs()
{
queue<int>q;
memset(d,-1,sizeof(d));
d[s]=0;
q.push(s);
head[s]=h[s];
while(q.size())
{
int f=q.front();
q.pop();
for(int i=h[f];~i;i=ne[i])
{
int v=e[i];
if(d[v]==-1&&c[i])
{
d[v]=d[f]+1;
head[v]=h[v];
if(v==t)
return true;
q.push(v);
}
}
}
return false;
}
int find(int u,int minn)
{
if(u==t)
return minn;
int res=0;
for(int i=head[u];~i&&res<minn;i=ne[i])
{
head[u]=i;
int v=e[i];
if(d[v]==d[u]+1&&c[i])
{
int temp=find(v,min(c[i],minn-res));
if(!temp)
d[v]=-1;
c[i]-=temp;
c[i^1]+=temp;
res+=temp;
}
}
return res;
}
int dinic()
{
int res=0,sum;
while(bfs())
while(sum=find(s,1e9))
res+=sum;
return res;
}
int main()
{
int n;
scanf("%d",&n);
t=n*2+1;
memset(h,-1,sizeof(h));
for(int i=1;i<=n;i++)
scanf("%d",&l[i]);
int sum=0;
for(int i=1;i<=n;i++)
{
add(i,i+n,1);
g[i]=1;
for(int j=1;j<i;j++)
if(l[j]<=l[i])
g[i]=max(g[i],g[j]+1);
for(int j=1;j<i;j++)
if(l[j]<=l[i]&&g[j]+1==g[i])
add(n+j,i,1);
sum=max(sum,g[i]);
if(g[i]==1)
add(s,i,1);
}
for(int i=1;i<=n;i++)
if(g[i]==sum)
add(n+i,t,1);
printf("%d\n",sum);
if(sum==1)
{
printf("%d\n%d",n,n);
return 0;
}
int res=dinic();
printf("%d\n",res);
for(int i=0;i<=idx;i+=2)
{
int u=e[i^1],v=e[i];
if(u==s&&(v==1||v==n))
c[i]=1e9;
if((u==1+n||u==n+n)&&v==t)
c[i]=1e9;
if(u==1&&v==n+1)
c[i]=1e9;
if(u==n&&v==n+n)
c[i]=1e9;
}
printf("%d",res+dinic());
return 0;
}
AcWing 2278
这个题就是把每两条可以到达的冰连一条容量为无限的边,冰的起跳限制拆点, s s s向每一个有企鹅的冰连一条容量为企鹅个数的边,表示给你这么多企鹅,枚举每个点当 t t t。
#include<bits/stdc++.h>
using namespace std;
const int NN=204,MM=20404,INF=1e9;
const double eps=1e-8;
int h[NN],ne[MM],e[MM],c[MM],d[NN],head[NN],idx,s,t;
double D;
pair<int,int>point[NN];
bool check(pair<int,int>a,pair<int,int>b)
{
double dx=a.first-b.first,dy=a.second-b.second;
return dx*dx+dy*dy<D*D+eps;
}
void add(int u,int v,int w)
{
e[++idx]=v;
ne[idx]=h[u];
c[idx]=w;
h[u]=idx;
e[++idx]=u;
ne[idx]=h[v];
c[idx]=0;
h[v]=idx;
}
bool bfs()
{
queue<int>q;
memset(d,-1,sizeof(d));
d[s]=0;
q.push(s);
head[s]=h[s];
while(q.size())
{
int f=q.front();
q.pop();
for(int i=h[f];~i;i=ne[i])
{
int v=e[i];
if(d[v]==-1&&c[i])
{
d[v]=d[f]+1;
head[v]=h[v];
if(v==t)
return true;
q.push(v);
}
}
}
return false;
}
int find(int u,int minn)
{
if(u==t)
return minn;
int res=0;
for(int i=head[u];~i&&res<minn;i=ne[i])
{
head[u]=i;
int v=e[i];
if(d[v]==d[u]+1&&c[i])
{
int temp=find(v,min(c[i],minn-res));
if(!temp)
d[v]=-1;
c[i]-=temp;
c[i^1]+=temp;
res+=temp;
}
}
return res;
}
int dinic()
{
int res=0,sum;
while(bfs())
while(sum=find(s,INF))
res+=sum;
return res;
}
int main()
{
int kase;
scanf("%d",&kase);
while(kase--)
{
memset(h,-1,sizeof(h));
idx=-1;
int n;
scanf("%d%lf",&n,&D);
int sum=0;
for(int i=1;i<=n;i++)
{
int x,y,a,b;
scanf("%d%d%d%d",&x,&y,&a,&b);
point[i]={
x,y};
add(s,i,a);
add(i,n+i,b);
sum+=a;
}
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(check(point[i],point[j]))
{
add(n+i,j,INF);
add(n+j,i,INF);
}
bool ok=false;
for(int i=1;i<=n;i++)
{
t=i;
for(int j=0;j<=idx;j+=2)
{
c[j]+=c[j^1];
c[j^1]=0;
}
if(dinic()==sum)
{
printf("%d ",i-1);
ok=true;
}
}
if(ok)
puts("");
else
puts("-1");
}
return 0;
}
非经典型
AcWing 2236——关键边
这个题可以先跑一遍最大流,可以发现当且仅当某一条 s s s到 t t t的路径上只有一条满流的边,那条唯一满流的边才需要重建。可以发现,每一条路径上至少有一条满流的路,要不然就还有增广路。那么就可以从 s s s或 t t t开始,标记有路径从 s s s或 t t t到该点且没经过满流的边的点(t走的和s走的边应当是相同的,并不是残留网络中用来后悔的反向边),再枚举每一条边,若 u u u被 s s s标记了, v v v被 t t t标记了,那么 u u u到 v v v一定是唯一的满流边。
#include<bits/stdc++.h>
using namespace std;
const int NN=504,MM=10004;
int h[NN],ne[MM],e[MM],c[MM],idx=-1,head[NN],d[NN],s,t;
bool vis_s[NN],vis_t[NN];
void add(int u,int v,int w)
{
ne[++idx]=h[u];
e[idx]=v;
c[idx]=w;
h[u]=idx;
ne[++idx]=h[v];
e[idx]=u;
h[v]=idx;
}
bool bfs()
{
queue<int>q;
q.push(s);
memset(d,-1,sizeof(d));
head[s]=h[s];
d[s]=0;
while(q.size())
{
int f=q.front();
q.pop();
for(int i=h[f];~i;i=ne[i])
{
int v=e[i];
if(d[v]==-1&&c[i])
{
head[v]=h[v];
d[v]=d[f]+1;
if(v==t)
return true;
q.push(v);
}
}
}
return false;
}
int find(int u,int sum)
{
if(u==t)
return sum;
int res=0;
for(int i=head[u];~i&&res<sum;i=ne[i])
{
head[u]=i;
int v=e[i];
if(d[v]==d[u]+1&&c[i])
{
int temp=find(v,min(c[i],sum-res));
if(!temp)
d[v]=-1;
c[i]-=temp;
c[i^1]+=temp;
res+=temp;
}
}
return res;
}
void dinic()
{
while(bfs())
while(find(s,1e9));
}
void dfs(int u,bool st[],int vis)
{
st[u]=true;
for(int i=h[u];~i;i=ne[i])
{
int j=i^vis,v=e[i];
if(!st[v]&&c[j])
dfs(v,st,vis);
}
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
memset(h,-1,sizeof(h));
t=n-1;
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
}
dinic();
dfs(s,vis_s,0);
dfs(t,vis_t,1);
int ans=0;
for(int i=0;i<=idx;i+=2)
if(vis_s[e[i^1]]&&vis_t[e[i]])
ans++;
printf("%d",ans);
return 0;
}
AcWing2277——最大流判定
求的是最长路径最小,可以二分这个长度。如果大于这个长度的边就可以走一次,反之就不能走。每次跑一遍最大流,如果最大流大于等于 k k k,那么就能走 k k k次,反之不能。
#include<bits/stdc++.h>
using namespace std;
const int NN=204,MM=80004;
int h[NN],ne[MM],e[MM],w[MM],c[MM],idx=-1,head[NN],d[NN],s=1,t,k;
bool vis_s[NN],vis_t[NN];
void add(int u,int v,int x)
{
ne[++idx]=h[u];
e[idx]=v;
w[idx]=x;
h[u]=idx;
ne[++idx]=h[v];
e[idx]=u;
w[idx]=x;
h[v]=idx;
}
bool bfs()
{
queue<int>q;
q.push(s);
memset(d,-1,sizeof(d));
head[s]=h[s];
d[s]=0;
while(q.size())
{
int f=q.front();
q.pop();
for(int i=h[f];~i;i=ne[i])
{
int v=e[i];
if(d[v]==-1&&c[i])
{
head[v]=h[v];
d[v]=d[f]+1;
if(v==t)
return true;
q.push(v);
}
}
}
return false;
}
int find(int u,int sum)
{
if(u==t)
return sum;
int res=0;
for(int i=head[u];~i&&res<sum;i=ne[i])
{
head[u]=i;
int v=e[i];
if(d[v]==d[u]+1&&c[i])
{
int temp=find(v,min(c[i],sum-res));
if(!temp)
d[v]=-1;
c[i]-=temp;
c[i^1]+=temp;
res+=temp;
}
}
return res;
}
int dinic()
{
int res=0,sum;
while(bfs())
while(sum=find(s,1e9))
res+=sum;
return res;
}
void dfs(int u,bool st[],int vis)
{
st[u]=true;
for(int i=h[u];~i;i=ne[i])
{
int j=i^vis,v=e[i];
if(!st[v]&&c[j])
dfs(v,st,vis);
}
}
bool check(int x)
{
for(int i=0;i<=idx;i++)
if(w[i]>x)
c[i]=0;
else
c[i]=1;
return dinic()>=k;
}
int main()
{
int n,m;
scanf("%d%d%d",&n,&m,&k);
memset(h,-1,sizeof(h));
t=n;
for(int i=1;i<=m;i++)
{
int u,v,x;
scanf("%d%d%d",&u,&v,&x);
add(u,v,x);
}
int l=1,r=1e9;
while(l<r)
{
int mid=l+(r-l)/2;
if(check(mid))
r=mid;
else
l=mid+1;
}
printf("%d",r);
return 0;
}
AcWing 2187——最大流判定
第零天向地球供给人数,每次可以在某一个地方等待一天,就是向表示后一天的该点连一条容量为正无穷的边。或者坐一次太空飞船,从表示当天的点 u u u的点,向表示下一天的点 v v v的点,连一条容量为飞船的容纳限制的边。不断累加最大流,直到哪天流量有 k k k为止。边是每次天数增加不断连的。为了一天只能走一步,日期不同的点的编号不一样,都是从第 i i i天的点连向第 i + 1 i+1 i+1天的点。
#include<bits/stdc++.h>
using namespace std;
const int NN=10004,MM=(1e5+NN)*2,KK=34;
int e[MM],ne[MM],c[MM],h[NN],fa[KK],idx=-1,head[NN],d[NN],s=NN-2,t=NN-1,n,m,k;
struct node
{
int l,r,id[KK];
}work[KK];
int find(int x)
{
return fa[x]==x?x:find(fa[x]);
}
int calc(int x,int day)
{
return day*(n+2)+x;
}
void add(int u,int v,int w)
{
e[++idx]=v;
ne[idx]=h[u];
c[idx]=w;
h[u]=idx;
e[++idx]=u;
ne[idx]=h[v];
h[v]=idx;
}
bool bfs()
{
queue<int>q;
memset(d,-1,sizeof(d));
q.push(s);
d[s]=0;
head[s]=h[s];
while(q.size())
{
int f=q.front();
q.pop();
for(int i=h[f];~i;i=ne[i])
{
int v=e[i];
if(d[v]==-1&&c[i])
{
d[v]=d[f]+1;
head[v]=h[v];
if(v==t)
return true;
q.push(v);
}
}
}
return false;
}
int find(int u,int sum)
{
if(u==t)
return sum;
int res=0;
for(int i=head[u];~i&&res<sum;i=ne[i])
{
int v=e[i];
head[u]=i;
if(d[v]==d[u]+1&&c[i])
{
int t=find(v,min(c[i],sum-res));
if(!t)
d[v]=-1;
c[i]-=t;
c[i^1]+=t;
res+=t;
}
}
return res;
}
int dinic()
{
int res=0,sum;
while(bfs())
while(sum=find(s,1e9))
res+=sum;
return res;
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
memset(h,-1,sizeof(h));
for(int i=0;i<30;i++)
fa[i]=i;
for(int i=0;i<m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
work[i]={
a,b};
for(int j=0;j<b;j++)
{
int id;
scanf("%d",&id);
if(id==-1)
id=n+1;
work[i].id[j]=id;
if(j)
fa[find(work[i].id[j-1])]=find(id);
}
}
if(find(0)!=find(n+1))
{
printf("0");
return 0;
}
add(s,calc(0,0),k);
add(calc(n+1,0),t,1e9);
int day=1,res=0;
while(1)
{
add(calc(n+1,day),t,1e9);
for(int i=0;i<=n+1;i++)
add(calc(i,day-1),calc(i,day),1e9);
for(int i=0;i<m;i++)
{
int r=work[i].r;
add(calc(work[i].id[(day-1)%r],day-1),calc(work[i].id[day%r],day),work[i].l);
}
res+=dinic();
if(res>=k)
break;
day++;
}
printf("%d",day);
return 0;
}
习题
AcWing 2237
解析和代码在下一篇博客——最小割给出