网络流

\(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;
}
posted @ 2023-03-06 14:25  Bertidurlah  阅读(23)  评论(0)    收藏  举报