网络流,主要用来解决流量问题。
常见的有最大流,与最小割。
网络流的之所以成为了省选算法,难就难在建模上。
因为这是第一篇,所以先介绍一下基础概念和模板写法。
详细的概念知识可以看大佬博客
我在这里放几个基础概念。
网络流:
所有弧上流量的集合f={f(u,v)},称为该容量网络的一个网络流.
定义:带权的有向图G=(V,E),满足以下条件,则称为网络流图(flow network):
- 仅有一个入度为0的顶点s,称s为源点
- 仅有一个出度为0的顶点t,称t为汇点
- 每条边的权值都为非负数,称为该边的容量,记作c(i,j)。
- 弧的流量:通过容量网络G中每条弧< u,v >,上的实际流量(简称流量),记为f(u,v)
性质
对于任意一个时刻,设f(u,v)实际流量,则整个图G的流网络满足3个性质:
- 容量限制:对任意u,v∈V,f(u,v)≤c(u,v)。
- 反对称性:对任意u,v∈V,f(u,v) = -f(v,u)。从u到v的流量一定是从v到u的流量的相反值。
- 流守恒性:对任意u,若u不为S或T,一定有∑f(u,v)=0,(u,v)∈E。即u到相邻节点的流量之和为0,因为流入u的流量和u点流出的流量相等,u点本身不会"制造"和"消耗"流量。
最大流
对于网络流图G,流量最大的可行流f,称为最大流
无向图的割集(Cut Set):C[A,B]是将图G分为A和B两个点集 A和B之间的边的全集 网络的割集:C[S,T]是将网络G分为s和t两部分点集 S属于s且T属于t 从S到T的边的全集 带权图的割(Cut):就是割集中边或者有向边的权和
通俗的理解一下: 割集好比是一个恐怖分子 把你家和自来水厂之间的水管网络砍断了一些 然后自来水厂无论怎么放水 水都只能从水管断口哗哗流走了 你家就停水了 割的大小应该是恐怖分子应该关心的事 毕竟细管子好割一些 而最小割花的力气最小
最大流最小割定理:
在容量网络中,满足弧流量限制条件,且满足平衡条件并且具有最大流量的可行流,称为网络最大流,简称最大流.
基础的一些概念解释完毕,那么正片开始。
我们来介绍两个算法:
1、EK:
这个算法应该是最好理解的算法。(对蒟蒻我而言)
首先我们要明确,我们现在要求的是最大流(即最小割)。
也就是说从s->t的路径中满足f(u,v)<=c(u,v);
一个很显而易见的思想就是,我们每一次通过bfs找一条增广路,然后进行增广。
然而在增广的过程中,可能会之前流来的水占用了一条管子,导致当前的水无法向前流。
这时候我们用加反向边来解决,每次增广后,让增广路上的正向边+minflow,反向边-minflow。
然后在下次增广时如果碰到了反向边>0,就说明可以试着让之前增广来的这条路径,回过头去找一下是否还有另外一条路径。
可能讲的不是很清楚,就有点儿像优化回溯的感觉。
然后一直增广,直到找不到增广路时,就得到了最大流。
注意一下边从偶数开始建,这样可以用i^1表示i的反向边。
代码
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#define ll long long
#define space putchar(' ')
#define endl putchar('\n')
#define debug puts("------------------------")
#define F(i,x,n) for(int i=x;i<=n;++i)
#define F_(i,x,n) for(int i=x;i>=n;--i)
using namespace std;
inline void read(int &a) {a=0;int c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();a*=b; }
inline int Rem() {int a=0,c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();return a*=b; }
inline void write(int x) {if(x>9)write(x/10);putchar('0'+x%10);}
inline void W(int x) {if(x<0){putchar('-'),x=-x;}write(x);}
/**/
const int N=1e4+5,M=1e5+5,inf=0x3f3f3f3f;
int n,m,s,t,head[N],cnt=1;
bool vis[N];
struct edge
{
int nxt,to,c;
}e[M<<1];
struct rec
{
int from,ed;
}pre[N];
/**/
inline void add(int u,int v,int c)
{
e[++cnt]=(edge){head[u],v,c};
head[u]=cnt;
}
bool bfs()
{
queue<int>q;
memset(vis,0,sizeof(vis));
memset(pre,0,sizeof(pre));
vis[s]=1;
q.push(s);
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(!vis[v]&&e[i].c)
{
pre[v].from=u;
pre[v].ed=i;
if(v==t) return 1;
vis[v]=1;
q.push(v);
}
}
}
return 0;
}
int EK()
{
int maxflow=0;
while(bfs())
{
int mi=inf;
for(int i=t;i!=s;i=pre[i].from)
{
mi=min(mi,e[pre[i].ed].c);
}
for(int i=t;i!=s;i=pre[i].from)
{
e[pre[i].ed].c-=mi;
e[pre[i].ed^1].c+=mi;
}
maxflow+=mi;
}
return maxflow;
}
int main()
{
read(n);read(m);read(s);read(t);
for(int i=1,a,b,c;i<=m;i++)
{
read(a);read(b);read(c);
add(a,b,c);
add(b,a,0);
}
cout<<EK();
return 0;
}
最小费用最大流的EK就是把bfs改成spfa按费用跑最短路:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#define ll long long
#define space putchar(' ')
#define endl putchar('\n')
#define debug puts("------------------------")
#define F(i,x,n) for(int i=x;i<=n;++i)
#define F_(i,x,n) for(int i=x;i>=n;--i)
using namespace std;
inline void read(int &a) {a=0;int c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();a*=b; }
inline int Rem() {int a=0,c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();return a*=b; }
inline void write(int x) {if(x>9)write(x/10);putchar('0'+x%10);}
inline void W(int x) {if(x<0){putchar('-'),x=-x;}write(x);}
/**/
const int N=5005,M=50005,inf=0x3f3f3f3f;
int n,m,s,t,head[N],cnt=1,dis[N],mi;
bool vis[N];
struct edge
{
int nxt,to,c,w;
}e[M<<1];
struct PRE
{
int from,edge;
}pre[N];
/**/
inline void add(int u,int v,int c,int w)
{
e[++cnt]=(edge){head[u],v,c,w};
head[u]=cnt;
}
bool bfs()
{
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
memset(pre,0,sizeof(pre));
vis[s]=1;dis[s]=0;
queue<int>q;
q.push(s);
while(!q.empty())
{
int u=q.front();q.pop();vis[u]=0;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(dis[v]>dis[u]+e[i].w&&e[i].c)
{
dis[v]=dis[u]+e[i].w;
pre[v].from=u;
pre[v].edge=i;
if(!vis[v])
{
vis[v]=1;
q.push(v);
}
}
}
}
return dis[t]!=inf;
}
void EK()
{
int maxflow=0,mincost=0;
while(bfs())
{
int mi=inf;
for(int i=t;i!=s;i=pre[i].from)
{
mi=min(mi,e[pre[i].edge].c);
}
for(int i=t;i!=s;i=pre[i].from)
{
e[pre[i].edge].c-=mi;
e[pre[i].edge^1].c+=mi;
}
maxflow+=mi;
mincost+=mi*dis[t];
}
W(maxflow),space,W(mincost),endl;
}
int main()
{
// freopen("test.in","r",stdin);
// freopen("test.out","w",stdout);
read(n);read(m);read(s);read(t);
for(int i=1,a,b,c,d;i<=m;i++)
{
read(a);read(b);read(c);read(d);
add(a,b,c,d);
add(b,a,0,-d);
}
EK();
return 0;
}
2、Dinic
我们来回想一下上面的EK。
每一次都只找了一条增广路进行增广。
那么我们有没有办法可以一次找尽可能多的增广路进行增广呢?
于是就有了Dinic这个算法。
我们通过bfs把每个点的深度跑出来,再根据深度把整个图分层。
然后通过dfs进行增广。
具体讲解见洛谷日报:Dinic详解
然后Dinic可以通过弧优化跑得飞快,弧优化原理也很简单:
首先,我们在按顺序dfs时,先被遍历到的边肯定是已经增广过了(或者已经确定无法继续增广了),那么这条边就可以视为“废边”
那么下次我们再到达该节点时,就可以直接无视掉所有废边,只走还有用的边,也就是说,每次dfs结束后,下次dfs可以更省时间。
以下分别是Dinic的普通->优化->弧优版本。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#define ll long long
#define space putchar(' ')
#define endl putchar('\n')
#define debug puts("------------------------")
#define F(i,x,n) for(int i=x;i<=n;++i)
#define F_(i,x,n) for(int i=x;i>=n;--i)
using namespace std;
inline void read(int &a) {a=0;int c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();a*=b; }
inline int Rem() {int a=0,c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();return a*=b; }
inline void write(int x) {if(x>9)write(x/10);putchar('0'+x%10);}
inline void W(int x) {if(x<0){putchar('-'),x=-x;}write(x);}
/**/
const int N=1e4+5,M=1e5+5,inf=0x3f3f3f3f;
int n,m,s,t,head[N],cnt=1,dep[N];
bool vis[N];
struct edge
{
int nxt,to,c;
}e[M<<1];
/**/
inline void add(int u,int v,int c)
{
e[++cnt]=(edge){head[u],v,c};
head[u]=cnt;
}
bool bfs()
{
memset(dep,0x3f,sizeof(dep));
memset(vis,0,sizeof(vis));
dep[s]=0;
vis[s]=1;
queue<int>q;
q.push(s);
while(!q.empty())
{
int u=q.front();q.pop();vis[u]=0;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(dep[v]>dep[u]+1&&e[i].c)
{
dep[v]=dep[u]+1;
if(!vis[v])
{
q.push(v);
vis[v]=1;
}
}
}
}
return dep[t]!=inf;
}
int dfs(int u,int flow)
{
int mi=0;
if(u==t) return flow;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(dep[v]==dep[u]+1&&e[i].c)
{
if((mi=dfs(v,min(flow,e[i].c)))!=0)
{
e[i].c-=mi;
e[i^1].c+=mi;
return mi;
}
}
}
return 0;
}
void Dinic()
{
int mi=0,maxflow=0;
while(bfs())
{
while((mi=dfs(s,inf))!=0) maxflow+=mi;
}
W(maxflow);
}
int main()
{
read(n);read(m);read(s);read(t);
for(int i=1,a,b,c;i<=m;i++)
{
read(a);read(b);read(c);
add(a,b,c);
add(b,a,0);
}
Dinic();
}
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#define ll long long
#define space putchar(' ')
#define endl putchar('\n')
#define debug puts("------------------------")
#define F(i,x,n) for(int i=x;i<=n;++i)
#define F_(i,x,n) for(int i=x;i>=n;--i)
using namespace std;
inline void read(int &a) {a=0;int c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();a*=b; }
inline int Rem() {int a=0,c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();return a*=b; }
inline void write(int x) {if(x>9)write(x/10);putchar('0'+x%10);}
inline void W(int x) {if(x<0){putchar('-'),x=-x;}write(x);}
/**/
const int N=1e4+5,M=1e5+5,inf=0x3f3f3f3f;
int n,m,s,t,head[N],cnt=1,dep[N],maxflow;
bool vis[N],flag;
struct edge
{
int nxt,to,c;
}e[M<<1];
/**/
inline void add(int u,int v,int c)
{
e[++cnt]=(edge){head[u],v,c};
head[u]=cnt;
}
bool bfs()
{
memset(dep,0x3f,sizeof(dep));
memset(vis,0,sizeof(vis));
dep[s]=0;
vis[s]=1;
queue<int>q;
q.push(s);
while(!q.empty())
{
int u=q.front();q.pop();vis[u]=0;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(dep[v]>dep[u]+1&&e[i].c)
{
dep[v]=dep[u]+1;
if(!vis[v])
{
q.push(v);
vis[v]=1;
}
}
}
}
return dep[t]!=inf;
}
int dfs(int u,int flow)
{
if(u==t)
{
flag=1;
maxflow+=flow;
return flow;
}
int used=0,mi=0;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(dep[v]==dep[u]+1&&e[i].c)
{
if((mi=dfs(v,min(flow-used,e[i].c)))!=0)
{
used+=mi;
e[i].c-=mi;
e[i^1].c+=mi;
if(used==flow) break;
}
}
}
return used;
}
void Dinic()
{
while(bfs())
{
flag=1;
while(flag)
{
flag=0;
dfs(s,inf);
}
}
W(maxflow);
}
int main()
{
read(n);read(m);read(s);read(t);
for(int i=1,a,b,c;i<=m;i++)
{
read(a);read(b);read(c);
add(a,b,c);
add(b,a,0);
}
Dinic();
}
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#define ll long long
#define space putchar(' ')
#define endl putchar('\n')
#define debug puts("------------------------")
#define F(i,x,n) for(int i=x;i<=n;++i)
#define F_(i,x,n) for(int i=x;i>=n;--i)
using namespace std;
inline void read(int &a) {a=0;int c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();a*=b; }
inline int Rem() {int a=0,c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();return a*=b; }
inline void write(int x) {if(x>9)write(x/10);putchar('0'+x%10);}
inline void W(int x) {if(x<0){putchar('-'),x=-x;}write(x);}
/**/
const int N=1e4+5,M=1e5+5,inf=0x3f3f3f3f;
int n,m,s,t,head[N],cnt=1,dep[N],maxflow,cur[N];
bool vis[N],flag;
struct edge
{
int nxt,to,c;
}e[M<<1];
/**/
inline void add(int u,int v,int c)
{
e[++cnt]=(edge){head[u],v,c};
head[u]=cnt;
}
bool bfs()
{
// memset(dep,0x3f,sizeof(dep));
// memset(vis,0,sizeof(vis));
F(i,1,n) cur[i]=head[i],dep[i]=inf,vis[i]=0;
dep[s]=0;
vis[s]=1;
queue<int>q;
q.push(s);
while(!q.empty())
{
int u=q.front();q.pop();vis[u]=0;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(dep[v]>dep[u]+1&&e[i].c)
{
dep[v]=dep[u]+1;
if(!vis[v])
{
q.push(v);
vis[v]=1;
}
}
}
}
return dep[t]!=inf;
}
int dfs(int u,int flow)
{
if(u==t)
{
flag=1;
maxflow+=flow;
return flow;
}
int used=0,mi=0;
for(int i=cur[u];i;i=e[i].nxt)
{
cur[u]=i;
int v=e[i].to;
if(dep[v]==dep[u]+1&&e[i].c)
{
if((mi=dfs(v,min(flow-used,e[i].c)))!=0)
{
used+=mi;
e[i].c-=mi;
e[i^1].c+=mi;
if(used==flow) break;
}
}
}
return used;
}
void Dinic()
{
while(bfs())
{
flag=1;
while(flag)
{
flag=0;
dfs(s,inf);
}
}
W(maxflow);
}
int main()
{
read(n);read(m);read(s);read(t);
for(int i=1,a,b,c;i<=m;i++)
{
read(a);read(b);read(c);
add(a,b,c);
add(b,a,0);
}
Dinic();
}
至于最小费用最大流就基本上差不多了,在dfs过程中加上vis,防止多次遍历就好。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#define ll long long
#define space putchar(' ')
#define endl putchar('\n')
#define debug puts("------------------------")
#define F(i,x,n) for(int i=x;i<=n;++i)
#define F_(i,x,n) for(int i=x;i>=n;--i)
using namespace std;
inline void read(int &a) {a=0;int c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();a*=b; }
inline int Rem() {int a=0,c=getchar(),b=1; while(c>'9'||c<'0') {if(c=='-')b=-1;c=getchar();} while(c>='0'&&c<='9') a=(a<<3)+(a<<1)+c-48,c=getchar();return a*=b; }
inline void write(int x) {if(x>9)write(x/10);putchar('0'+x%10);}
inline void W(int x) {if(x<0){putchar('-'),x=-x;}write(x);}
/**/
const int N=5005,M=50005,inf=0x3f3f3f3f;
int n,m,s,t,dis[N],maxflow,mincost,cnt=1,head[N];
bool vis[N];
struct edge
{
int nxt,to,c,w;
}e[M<<1];
/**/
inline void add(int u,int v,int c,int w)
{
e[++cnt]=(edge){head[u],v,c,w};
head[u]=cnt;
}
bool spfa()
{
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
queue<int>q;
vis[s]=1,dis[s]=0;
q.push(s);
while(!q.empty())
{
int u=q.front();vis[u]=0;q.pop();
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(dis[v]>dis[u]+e[i].w&&e[i].c)
{
dis[v]=dis[u]+e[i].w;
if(!vis[v])
{
q.push(v);
vis[v]=1;
}
}
}
}
return dis[t]!=inf;
}
int dfs(int u,int flow)
{
if(u==t)
{
vis[t]=1;
maxflow+=flow;
return flow;
}
int used=0,minflow=0;
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if((v==t||!vis[v])&&e[i].c&&dis[v]==dis[u]+e[i].w)
{
if((minflow=dfs(v,min(flow-used,e[i].c)))!=0)
{
mincost+=e[i].w*minflow;
used+=minflow;
e[i].c-=minflow;
e[i^1].c+=minflow;
if(used==flow) break;
}
}
}
return used;
}
void Dinic()
{
while(spfa())
{
vis[t]=1;
while(vis[t])
{
memset(vis,0,sizeof(vis));
dfs(s,inf);
}
}
cout<<maxflow<<' '<<mincost<<'\n';
}
int main()
{
// freopen("test.in","r",stdin);
// freopen("test.out","w",stdout);
read(n);read(m);read(s);read(t);
for(int i=1,a,b,c,d;i<=m;i++)
{
read(a);read(b);read(c);read(d);
add(a,b,c,d);
add(b,a,0,-d);
}
Dinic();
}
END~
BY:楚泫
DATE:2019/3/13 19:46