网络流
\(OI-wiki\)
洛谷日报 用最通俗的语言让你学会网络流\((EK)\)
很详细的\(blog\)
P3376 【模板】网络最大流
最大流:求一张图中从源点流向汇点的最大流量(可以有很多条路到达汇点)。
增广路:增广路是指从 \(s\) 到 \(t\) 的一条路,流过这条路,使得当前的流可以增加。
\(EK(Edmond—Karp)\)
复杂度:\(O(|V||E|^2)\) (似乎不是很优秀)
- \(bfs\)判断是否存在增广路。
- 反向建边,正向加上\(Min\),反向减去\(Min\)。
点击查看代码
//EK
#include<bits/stdc++.h>
#define cs const
#define il inline
#define ri register
#define pc(i) putchar(i)
using namespace std;typedef int I;typedef long long LL;LL FL,CH;template<typename T>bool in(T&a){for(FL=1;!isdigit(CH)&&CH!=EOF;CH=getchar())if(CH=='-')FL=-1;for(a=0;isdigit(CH);CH=getchar())a=a*10+CH-'0';return a*=FL,CH==EOF?0:1;}template<typename T,typename...Args>LL in(T&a,Args&...args){return in(a)+in(args...);}
#define int long long
cs int inf=1e9+8,N=1e5+9;
struct Pre{int v,e;}pre[N];
struct node{int to,w,nxt;}e[N<<1];
int n,m,s,t,top=1,h[N],inq[N];
il void add(cs int u,cs int v,cs int w){e[++top]={v,w,h[u]},h[u]=top;}
il bool bfs()//是否有增广路
{
queue<int>q;
memset(inq,0,sizeof(inq));
inq[s]=1,q.push(s);
while(!q.empty())
{
int u=q.front(); q.pop();
for(ri int i=h[u],to;i;i=e[i].nxt)
if((!inq[to=e[i].to])&&e[i].w)
{
pre[to]={u,i};
if(to==t) return 1;
inq[to]=1,q.push(to);
}
}
return 0;
}
il int EK()
{
int ans=0;
while(bfs())
{
int Min=inf;
for(ri int i=t;i!=s;i=pre[i].v)
Min=min(Min,e[pre[i].e].w);
for(ri int i=t;i!=s;i=pre[i].v)
e[pre[i].e].w-=Min,e[pre[i].e^1].w+=Min;
ans+=Min;
}
return ans;
}
signed main()
{
in(n,m,s,t);
for(ri int i=1,u,v,w;i<=m;++i)
in(u,v,w),add(u,v,w),add(v,u,0);
printf("%lld",EK()); //qaq
return 0;
}
\(Dinic\)
复杂度:\(O(|V|^2|E|)\) (在一些性质良好的图上有更好的时间复杂度)
- \(bfs\)分层,建图同\(EK\)。
- \(dfs\)对于层数\(+1\)的边进行增广。
- 多路增广\(and\)当前弧优化。
点击查看代码
//Dinic
#include<bits/stdc++.h>
#define il inline
#define cs const
#define fo(i,j,k) for(int (i)=(j);(i)<=(k);++(i))
#define of(i,j,k),for(int (i)=(j);(i)>=(k);--(i))
using namespace std;typedef long long LL;const LL inf=1e18+7;int FL,CH;template<typename T>bool in(T&a){for(FL=1;!isdigit(CH)&&CH!=EOF;CH=getchar())if(CH=='-')FL=-1;for(a=0;isdigit(CH);CH=getchar())a=a*10+CH-'0';return a*=FL,CH==EOF?0:1;}template<typename T,typename...Args>int in(T&a,Args&...args){return in(a)+in(args...);}
cs int N=2e4+7;
struct node{int to,nxt;LL w;}e[N<<1];
int h[N],n,m,s,t,eoe=1,dep[N],cur[N];
il void add(cs int u,cs int v,cs LL w){e[++eoe]={v,h[u],w},h[u]=eoe;}
il bool bfs()
{
fo(i,1,n) dep[i]=-1;
queue<int>q; q.push(s),dep[s]=0,cur[s]=h[s];
while(!q.empty())
{
int u=q.front(); q.pop();
for(int to,i=h[u];i;i=e[i].nxt)
if(dep[to=e[i].to]==-1&&e[i].w)
{
dep[to]=dep[u]+1,cur[to]=h[to];
if(to==t) return 1;
q.push(to);
}
}
return 0;
}
LL dfs(int u,LL Max)
{
if(u==t) return Max;
LL flow=0;
for(int i=cur[u],to;i&&flow<Max;i=e[i].nxt)
{
to=e[i].to,cur[u]=i;
if(dep[to]==dep[u]+1&&e[i].w)
{
LL k=dfs(to,min(e[i].w,Max-flow));
if(!k) dep[to]=-1;
e[i].w-=k,e[i^1].w+=k,flow+=k;
}
}
return flow;
}
il LL Dinic(){LL re=0; while(bfs()) re+=dfs(s,inf); return re;}
signed main()
{
in(n,m,s,t);
while(m--){int u,v;LL w;in(u,v,w),add(u,v,w),add(v,u,0);}
printf("%lld",Dinic());
return 0;
}
P3381 【模板】最小费用最大流
最小费用最大流:每条边都有一个费用,代表单位流量流过这条边的开销。我们要在求出最大流的同时,要求花费的费用最小。
- \(bfs\)换成\(spfa\)。
- 反向边的费用是正向边的相反数。
点击查看代码
//EK
#include<bits/stdc++.h>
#define ri register
#define cs const
#define il inline
using namespace std;typedef long long LL;int FL,CH;template<typename T>bool in(T&a){for(FL=1;!isdigit(CH)&&CH!=EOF;CH=getchar())if(CH=='-')FL=-1;for(a=0;isdigit(CH);CH=getchar())a=a*10+CH-'0';return a*=FL,CH==EOF?0:1;}template<typename T,typename...Args>int in(T&a,Args&...args){return in(a)+in(args...);}
cs int N=5e4+7,inf=1e9+7;
struct node{int to,w,nxt,val;}e[N<<1];//w费用 val流量
struct Pre{int fa,e;}pre[N];
int n,m,s,t,h[N],eoe=1,dis[N],inq[N],maf,mic;
il void add(cs int u,cs int v,cs int val,cs int w){e[++eoe]={v,w,h[u],val},h[u]=eoe;}
il bool spfa()
{
for(ri int i=1;i<=n;++i) inq[i]=0,dis[i]=inf;
queue<int>q; inq[s]=1,dis[s]=0,q.push(s);
while(!q.empty())
{
int u=q.front(); inq[u]=0,q.pop();
for(ri int i=h[u],to;i;i=e[i].nxt)
if(dis[to=e[i].to]>dis[u]+e[i].w&&e[i].val)
{
dis[to]=dis[u]+e[i].w,pre[to]={u,i};
if(!inq[to]) q.push(to),inq[to]=1;
}
}
return dis[t]!=inf;
}
il void EK()
{
while(spfa())
{
int Min=inf;
for(ri int i=t;i!=s;i=pre[i].fa)
Min=min(Min,e[pre[i].e].val);
for(ri int i=t;i!=s;i=pre[i].fa)
e[pre[i].e].val-=Min,e[pre[i].e^1].val+=Min;
maf+=Min,mic+=Min*dis[t];
}
}
signed main()
{
in(n,m,s,t); int u,v,val,w;
while(m--) in(u,v,val,w),add(u,v,val,w),add(v,u,0,-w);
EK(),printf("%d %d",maf,mic);
return 0;
}
最小割
- 最大流最小割定理:\(f(s,t)_{max}=c(s,t)_{min}\)
P1344 [USACO4.4] 追查坏牛奶 Pollutant Control
- \(q1\)相当于求最大流。
- 只需建图时将边权\(w=w*a+1\)(\(w\)为本来的边权,\(a\)为大于\(1000\)的数),\(a{\div} Mod\)为最小割,\(a\pmod Mod\)为最小割边数。
- 求割边数量:满足最小割的前提下,最小化割边的数量(即删除的边的数量)。首先跑最大流求出最小割,然后将没有满流的边容量改成 \(inf\) ,并将满流了的边的容量改为 \(1\) ,重新跑一遍最小割,求出来的即是最小割边数。如果没有最小割,则直接将所有边的容量都设为 \(1\) 并跑最小割。
- 更详细的求割边数量的解法
点击查看代码
#include<bits/stdc++.h>
#define il inline
#define ri register
#define cs const
#define pc(i) putchar(i)
using namespace std;typedef long long LL;const LL inf=0x3f3f3f3f;LL FL,CH;template<typename T>bool in(T&a){for(FL=1;!isdigit(CH)&&CH!=EOF;CH=getchar())if(CH=='-')FL=-1;for(a=0;isdigit(CH);CH=getchar())a=a*10+CH-'0';return a*=FL,CH==EOF?0:1;}template<typename T,typename...Args>LL in(T&a,Args&...args){return in(a)+in(args...);}
cs LL N=1e3+7,Mod=1111;
LL ans;//long long qwq
int n,m,s=1,t,h[N],eoe=1,inq[N];
struct Pre{int fa,e;}pre[N];
struct node{int to,nxt;LL w;}e[N<<1];;
il void add(cs int u,cs int v,cs LL w){e[++eoe]={v,h[u],w},h[u]=eoe;}
il bool bfs()
{
for(ri int i=1;i<=n;++i) inq[i]=0;
queue<int>q; q.push(s),inq[s]=1;
while(!q.empty())
{
int u=q.front();q.pop();
for(ri int to,i=h[u];i;i=e[i].nxt)
if((!inq[to=e[i].to])&&e[i].w)
{
pre[to]={u,i};
if(to==t) return 1;
inq[to]=1,q.push(to);
}
}
return 0;
}
il void EK()
{
while(bfs())
{
LL Min=inf;
for(ri int i=t;i!=s;i=pre[i].fa)
Min=min(Min,e[pre[i].e].w);
for(ri int i=t;i!=s;i=pre[i].fa)
e[pre[i].e].w-=Min,e[pre[i].e^1].w+=Min;
ans+=Min;
}
}
int main()
{
in(n,m),t=n;
for(ri int i=1,u,v,w;i<=m;++i)
in(u,v,w),add(u,v,w*Mod+1),add(v,u,0);
EK(),printf("%lld %lld",ans/Mod,ans%Mod);
return 0;
}
P1345 [USACO5.4]奶牛的电信Telecowmunication
- 求最小割点:将一个点拆成入点和出点,中间连一条容量为 \(1\) 的边,此时割掉这条边相当于割掉这个点,对于其它的边容量设为\(inf\)即可。值得注意的是,对于源点和汇点拆后连的边应为\(inf\)(毕竟你不能不要源点和汇点吧)。
- 然后这个题目就转化成了求最大流。注意双倍空间\(qwq\)。
通过这篇题解学会的,简洁好懂。
点击查看代码
#include<bits/stdc++.h>
#define il inline
#define ri register
#define cs const
using namespace std;typedef int I;typedef long long LL;const I inf=0x3f3f3f3f;I FL,CH;template<typename T>bool in(T&a){for(FL=1;!isdigit(CH)&&CH!=EOF;CH=getchar())if(CH=='-')FL=-1;for(a=0;isdigit(CH);CH=getchar())a=a*10+CH-'0';return a*=FL,CH==EOF?0:1;}template<typename T,typename...Args>I in(T&a,Args&...args){return in(a)+in(args...);}
cs int N=1111;
int h[N<<1],eoe=1,s,t,n,m,inq[N<<1];
struct Pre{int fa,e;}pre[N<<1];
struct node{int to,nxt,w;}e[N<<2];
il void add(cs int u,cs int v,cs int w){e[++eoe]={v,h[u],w},h[u]=eoe;}
il bool bfs()
{
memset(inq,0,sizeof(inq));
queue<I>q; q.push(s),inq[s]=1;
while(!q.empty())
{
int u=q.front(); q.pop();
for(ri int i=h[u],to;i;i=e[i].nxt)
if((!inq[to=e[i].to])&&e[i].w)
{
pre[to]={u,i};
if(to==t) return 1;
inq[to]=1,q.push(to);
}
}
return 0;
}
il int EK()
{
int ans=0;
while(bfs())
{
int Min=inf;
for(ri int i=t;i!=s;i=pre[i].fa)
Min=min(Min,e[pre[i].e].w);
for(ri int i=t;i!=s;i=pre[i].fa)
e[pre[i].e].w-=Min,e[pre[i].e^1].w+=Min;
ans+=Min;
}
return ans;
}
int main()
{
in(n,m,s,t);
for(ri int i=1;i<=n;++i)
if(i!=s&&i!=t) add(i,i+n,1),add(i+n,i,0);
else add(i,i+n,inf),add(i+n,i,0);
for(ri int i=1,u,v;i<=m;++i)
in(u,v),add(u+n,v,inf),add(v+n,u,inf);
printf("%d",EK());
return 0;
}
以上代码均用\(EK\)实现\(qwq\)
以下代码可能均由\(Dinic\)实现
原因:
P2598 [ZJOI2009]狼和羊的故事
- 源点向羊连 \(inf\),羊向狼连 \(1\),狼向汇点连 \(inf\),羊向中立连 \(1\),中立向中立连 \(1\),中立向狼连 \(1\)。
点击查看代码
#include<bits/stdc++.h>
#define cs const
#define il inline
#define ri register
#define fo(i,j,k) for(int (i)=(j);(i)<=(k);++(i))
#define of(i,j,k),for(int (i)=(j);(i)>=(k);--(i))
using namespace std;typedef pair<int,int> pii;typedef int I;const I inf=0x3f3f3f3f;I FL,CH;template<typename T>bool in(T&a){for(FL=1;!isdigit(CH)&&CH!=EOF;CH=getchar())if(CH=='-')FL=-1;for(a=0;isdigit(CH);CH=getchar())a=a*10+CH-'0';return a*=FL,CH==EOF?0:1;}template<typename T,typename...Args>I in(T&a,Args&...args){return in(a)+in(args...);}
cs int N=1e4+5,dir[2][4]={{-1,1,0,0},{0,0,1,-1}};
int n,m,g[111][111],h[N],eoe=1,inq[N],s,t,cur[N],dep[N];
struct Pre{int fa,e;}pre[N];
struct node{I to,nxt,w;}e[N<<3];
il void add(cs int u,cs int v,cs int w){e[++eoe]={v,h[u],w},h[u]=eoe;}
il int c(cs int i,cs int j){return (i-1)*m+j;}
il bool bfs()
{
fo(i,0,n*m+5) dep[i]=-1; dep[s]=0;
queue<int>q; q.push(s),dep[s]=0,cur[s]=h[s];
while(!q.empty())
{
int u=q.front();q.pop();
for(int to,i=h[u];i;i=e[i].nxt)
if(dep[to=e[i].to]==-1&&e[i].w)
{
dep[to]=dep[u]+1,cur[to]=h[to];
if(to==t) return 1; q.push(to);
}
}
return 0;
}
int dfs(int u,int Max)
{
if(u==t) return Max;
int flow=0;
for(int i=cur[u],to;i&&flow<Max;i=e[i].nxt)
{
to=e[i].to,cur[u]=i;
if(dep[to]==dep[u]+1&&e[i].w)
{
int k=dfs(to,min(e[i].w,Max-flow));
if(!k) dep[to]=-1;
e[i].w-=k,e[i^1].w+=k,flow+=k;
}
}
return flow;
}
il int Dinic(){int re=0; while(bfs()) re+=dfs(s,inf); return re;}
signed main()
{
// freopen("1.in","r",stdin);
in(n,m),s=n*m+2,t=n*m+1;
for(ri int i=1;i<=n;++i)
for(ri int j=1;j<=m;++j)
{
in(g[i][j]);
if(g[i][j]==1) add(s,c(i,j),inf),add(c(i,j),s,0);//inf
else if(g[i][j]==2) add(c(i,j),t,inf),add(t,c(i,j),0);//inf
}
for(ri int i=1;i<=n;++i)
for(ri int j=1;j<=m;++j)
{
if(g[i][j]==1||(!g[i][j]))
for(ri int k=0;k<4;++k)
{
int ni=i+dir[0][k],nj=j+dir[1][k];
if(ni>=1&&ni<=n&&nj>=1&&nj<=m&&g[ni][nj]!=1)
add(c(i,j),c(ni,nj),1),add(c(ni,nj),c(i,j),0);
}
}
printf("%d",Dinic());
return 0;
}
P3931 SAC E#1 - 一道难题 Tree
- 注意建边。
- 所有叶子节点都连条\(inf\)的边到汇点。
点击查看代码
#include<bits/stdc++.h>
#define cs const
#define il inline
#define fo(i,j,k) for(int i=(j);(i)<=(k);++(i))
#define of(i,j,k),for(int i=(j);(i)>=(k);--(i))
#define long long LL;
using namespace std;
const int inf=0x3f3f3f3f,N=1e5+7;
int FL,CH;
template<typename T> bool in(T&a)
{
for(FL=1;!isdigit(CH)&&CH!=EOF;CH=getchar())
if(CH=='-')FL=-1;
for(a=0;isdigit(CH);CH=getchar())
a=a*10+CH-'0';
return a*=FL,CH==EOF?0:1;
}
template<typename T,typename...Args>
int in(T&a,Args&...args){return in(a)+in(args...);}
int h[N],eoe=1,cur[N],dep[N],n,m,s,t,Dep[N];
struct node{int to,nxt,w;}e[N<<1];
il void add(cs int u,cs int v,cs int w){e[++eoe]={v,h[u],w},h[u]=eoe;}
il bool bfs()
{
fo(i,0,n+2) dep[i]=-1; dep[s]=0;
queue<int>q; q.push(s),dep[s]=0,cur[s]=h[s];
while(!q.empty())
{
int u=q.front();q.pop();
for(int to,i=h[u];i;i=e[i].nxt)
if(dep[to=e[i].to]==-1&&e[i].w)
{
dep[to]=dep[u]+1,cur[to]=h[to];
if(to==t) return 1; q.push(to);
}
}
return 0;
}
int dfs(int u,int Max)
{
if(u==t) return Max;
int flow=0;
for(int i=cur[u],to;i&&flow<Max;i=e[i].nxt)
{
to=e[i].to,cur[u]=i;
if(dep[to]==dep[u]+1&&e[i].w)
{
int k=dfs(to,min(e[i].w,Max-flow));
if(!k) dep[to]=-1;
e[i].w-=k,e[i^1].w+=k,flow+=k;
}
}
return flow;
}
void dfs1(cs int u,cs int Fa)
{
bool f=0;
for(int i=h[u];i;i=e[i].nxt)
if(e[i].to!=Fa) f=1,dfs1(e[i].to,u);
if(!f) add(u,t,inf),add(t,u,0);
}
il int Dinic(){int re=0; while(bfs()) re+=dfs(s,inf); return re;}
signed main()
{
in(n,s),t=n+1; int u,v,w;
fo(i,1,n-1) in(u,v,w),add(u,v,w),add(v,u,w);
dfs1(s,-1),printf("%d",Dinic());
return 0;
}
最大权闭合图
- 最大权值闭合图: 给定一张有向图,每个点都有一个权值(可以为正或负或 0),你需要选择一个权值和最大的子图,使得子图中每个点的后继都在子图中。
- 做法:
- 建立超级源点和超级汇点。
- 连源点和正权点。负权点和汇点。
- 连原来图内的边,边权改为\(inf\)。
- 最大权闭合图的权值等于正权点点权和减去从 \(S\) 到 \(T\) 的最大流。这里有证明。
P4174 [NOI2006] 最大获利
- 中转站 \(A\) 和 \(B\) 是获益 \(C\) 的前提,考虑从 \(A,B\) 向 \(C\) 连一条权值为 \(inf\) 的边。
- 考虑将成本转化成负权点,收入转化成正权点,分别与汇点和源点连边。
- \(ans=sum_+-c(s,t)\)。
- 双倍经验。
点击查看代码
#include<bits/stdc++.h>
#define cs const
#define il inline
#define fo(i,j,k) for(int i=(j);(i)<=(k);++(i))
#define of(i,j,k),for(int i=(j);(i)>=(k);--(i))
#define LL long long
cs LL inf=1e18+7;
LL FL,CH;
template<typename T>bool in(T&a)
{
for(FL=1;!isdigit(CH)&&CH!=EOF;CH=getchar())
if(CH=='-')FL=-1;
for(a=0;isdigit(CH);CH=getchar())
a=a*10+CH-'0';
return a*=FL,CH==EOF?0:1;
}
template<typename T,typename...Args>
LL in(T&a,Args&...args){return in(a)+in(args...);}
cs int N=1e6+7;
LL sum;
int n,m,dep[N],cur[N],eoe=1,h[N],s,t;//qwq
struct node{int to,nxt;LL w;}e[N<<1];
il void add(cs int u,cs int v,cs LL w)
{
e[++eoe]={v,h[u],w},h[u]=eoe,
e[++eoe]={u,h[v],0},h[v]=eoe;
}
il bool bfs()
{
fo(i,0,n+m+2) dep[i]=-1; dep[s]=0;
std::queue<int>q; q.push(s),cur[s]=h[s];
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=h[u],to;i;i=e[i].nxt)
if(dep[to=e[i].to]==-1&&e[i].w)
{
cur[to]=h[to],dep[to]=dep[u]+1;
if(to==t) return 1; q.push(to);
}
}
return 0;
}
LL dfs(cs int u,cs LL Max)
{
if(u==t) return Max;
LL flow=0;
for(int to,i=cur[u];flow<Max&&i;i=e[i].nxt)
{
to=e[i].to,cur[u]=i;
if(dep[to]==dep[u]+1&&e[i].w)
{
LL k=dfs(to,std::min(Max-flow,e[i].w));
if(!k) dep[to]=-1;
e[i].w-=k,e[i^1].w+=k,flow+=k;
}
}
return flow;
}
il LL Dinic(){LL re=0;while(bfs()) re+=dfs(s,inf);return re;}
signed main()
{
in(n,m),t=n+m+1,s=0;
fo(i,1,n) {LL w;in(w),add(i+m,t,w);} //-成本
fo(i,1,m)
{
int u,v;LL w;in(u,v,w),add(s,i,w);
add(i,u+m,inf),add(i,v+m,inf),sum+=w;//+收入
}
printf("%lld",sum-Dinic());
return 0;
}

浙公网安备 33010602011771号