网络流
定义
网络流的定义是:一张有向图,由点集V和边集E组成,其中每条边都有流量(flow),容量(cap)。用f(u,v)和c(u,v)表示u->v的流量和容量。一个图是网络流需要满足以下条件:
1.有源点和汇点。
2.u->v不存在时,c(u,v)=0
3.f(u,v)<=c(u,v)。
4.除了源点和汇点,流入一个节点的流量=流出一个节点的流量。
和网络流联系紧密的是残量网络,记录r(u,v)=c(u,v)-f(u,v),表示u->v的残量,也就是这条边还能流进多少(可以想象成水管)。
后向弧
我们给每条u->v都反向建边,但这不是真正存在的边(所以容量=0),我们称之为后向弧。对于一条u->v的边,他的后向弧的流量是他流量的相反数。后向弧起到的作用是“撤销”,因为往后向弧加流量就等同于往真正的边减流量。
最大流
定义
从源点s到汇点t最大的流量。
增广路
从s到t的一条路径(该路径中可以存在后向弧),其中走过的边的残量r需均>0。
解决
最大流可以通过不停寻找增广路求得,当不存在增广路时,可以证明(并不会证明,要用最小割最大流定理证明,但太蒟蒻了,暂时还不会)此时s到t的流量即为最大流。
Edmond-Karp
思想
由于最大流可以通过不停寻找增广路求得,于是非常暴力的Edmond-Karp(简称EK)诞生了:直接bfs寻找增广路。
实现
EK算法概括如下:
当有最短增广路时:
1.bfs寻找增广路。
2.更改这条增广路流量(+最小的残量)。
否则,最大流求得。
可以证明(太蒟蒻了,不会证明),最多bfs总共n*m(n为节点个数,m为边数)次,又因为每次bfs的复杂度为O(m),所以总复杂度为O(n*m^2),但实际效果远小于此。
模板
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=505,maxm=40005,MAXINT=((1<<30)-1)*2+1;
struct Edge
{
int cap,flow,to,nxt;
};
struct fa
{
int who,e;
};
int n,m,E,st,gl,lnk[maxn],que[maxn],dis[maxn];
fa father[maxn];
Edge e[maxm];
bool vis[maxn];
void Add(int x,int y,int z)
{
e[E].to=y;e[E].cap=z;e[E].nxt=lnk[x];lnk[x]=E;E++; //加了一条边
e[E].to=x;e[E].cap=0;e[E].nxt=lnk[y];lnk[y]=E;E++; //后向弧
//这么建边,则j是真正的边,j^1是对应的后向弧
}
int Bfs() //bfs寻找增广路
{
int Head=0,Tail=0;memset(vis,0,sizeof(vis));
que[++Tail]=st;vis[st]=true;dis[Tail]=MAXINT;
while (Head!=Tail)
{
int x=que[++Head];
for (int j=lnk[x];j!=-1;j=e[j].nxt)
if (!vis[e[j].to]&&e[j].cap-e[j].flow>0) //残量必须>0
{
que[++Tail]=e[j].to;vis[e[j].to]=true;
father[e[j].to].who=x;father[e[j].to].e=j;
dis[Tail]=min(dis[Head],e[j].cap-e[j].flow);
if (e[j].to==gl) return dis[Tail]; //走到终点
}
}
return 0;
}
void change(int MIN) //修正
{
int now=gl;
while (father[now].who) //ÑØ×Åfather×ß
{
int j=father[now].e;
e[j].flow+=MIN;e[j^1].flow-=MIN; //j的流量多了MIN,同时j的相反边流量减少MIN
now=father[now].who;
}
}
int MAXflow() //求最大流
{
int MAX=0;
while (true)
{
int f=Bfs();
if (f==0) break;
change(f);MAX+=f;
}
return MAX;
}
int main()
{
freopen("EK.in","r",stdin);
freopen("EK.out","w",stdout);
scanf("%d%d",&n,&m);
memset(lnk,255,sizeof(lnk));
for (int i=1;i<=m;i++)
{
int x,y,z;scanf("%d%d%d",&x,&y,&z);
Add(x,y,z);
}
scanf("%d%d",&st,&gl);
printf("%d\n",MAXflow());
return 0;
}
Dinic
作用
EK的时间复杂度不是那么良好,这是由于寻找增广路的想法并不好,而寻找增广路又是求最大流所必须的。Dinic算法改善了寻找增广路的方法,能比较快速地求出最大流。
思想
Dinic分为两个步骤:1.求层次图。2.求阻塞流。
1.求层次图
顾名思义,层次图是一层一层的,第一层距离为0,第二层距离为1,第三层距离为2……举个例子,如下:
很明显用bfs就可以求出层次图(其实就是算出距离)。
2.求阻塞流
如果存在层次图(存在层次图等价于能从s到t),就在层次图中寻找增广路(只有i-1层的点才能到达i层),直到没有增广路为止。所有的这些增广路成为阻塞流。因为同时求很多增广路,所以我们可以用dfs实现,但要注意细节优化,详见代码。
初始层次图s到t的距离最小为1,每次求完阻塞流后s到t的距离至少+1,因为阻塞流会让层次图的至少一层断开(否则肯定还能找到增广路)。所以求层次图和阻塞流的次数最多为n-1(距离为n是不可能的,因为总共只有n个节点),而每次求阻塞流的时间复杂度是O(n*m)(每条边都被经过了n次),则Dinic的效率为O(n^2*m),和EK一样,实际效果远小于此。
比较
如下是POJ1459的时间对比。
第一个是Dinic,第二个是EK,可见Dinic快了非常多。
模板
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=500,maxm=40000,MAXINT=((1<<30)-1)*2+1;
int n,m,E,lnk[maxn+5],nxt[maxm+5],son[maxm+5];
int cur[maxn+5],que[maxn+5],dis[maxn+5];
bool vis[maxn+5];
struct Edge
{
int cap,flow;
};
Edge e[maxm+5];
void Add(int x,int y,int z)
{
son[E]=y;nxt[E]=lnk[x];lnk[x]=E;e[E].cap=z;e[E].flow=0;E++;
son[E]=x;nxt[E]=lnk[y];lnk[y]=E;e[E].cap=0;e[E].flow=0;E++;
}
bool Bfs(int st,int gl)
{
memset(vis,0,sizeof(vis));
int Head=0,Tail=0;que[++Tail]=st;vis[st]=true;dis[st]=1;
while (Head!=Tail)
{
int x=que[++Head];
for (int j=lnk[x];~j;j=nxt[j])
if (!vis[son[j]]&&e[j].cap>e[j].flow)
{
vis[son[j]]=true;dis[son[j]]=dis[x]+1;
que[++Tail]=son[j];
}
}
return vis[gl];
}
int Dfs(int x,int gl,int MIN) //MIN是目前的最小残量
{
if (x==gl||MIN==0) return MIN; //当MIN为0时及时退出,否则会有很多多余搜索
int flow=0;
for (int &j=cur[x];~j;j=nxt[j])
//从上一次继续做,而之前的不用管了因为早就处理完了
if (dis[x]+1==dis[son[j]]) //只能在层次图中增广
{
int now=Dfs(son[j],gl,min(MIN,e[j].cap-e[j].flow));
if (now)
{
e[j].flow+=now;e[j^1].flow-=now;
flow+=now;MIN-=now;
if (!MIN) break;
}
}
return flow;
}
int MAXflow(int s,int t)
{
int flow=0;
while (Bfs(s,t)) //层次图
{
memcpy(cur,lnk,sizeof(cur));
flow+=Dfs(s,t,MAXINT); //阻塞流
}
return flow;
}
int main()
{
freopen("Dinic.in","r",stdin);
freopen("Dinic.out","w",stdout);
scanf("%d%d",&n,&m);
memset(lnk,255,sizeof(lnk));
for (int i=1;i<=m;i++)
{
int x,y,z;scanf("%d%d%d",&x,&y,&z);
Add(x,y,z);
}
printf("%d\n",MAXflow(1,n));
return 0;
}