相关概念:node
给定指定的一个有向图,其中有两个特殊的源点S和汇点T,每条边有指定的容量,求知足条件的从S到T的最大流。ios
残量网络=容量网络-流量网络
概念就不讲了吧,顾名思义。算法
增广路: 设 f 是一个容量网络 G 中的一个可行流, P 是从 Vs 到 Vt 的一条链, 若 P 知足下列条件:网络
则称 P 为关于可行流 f 的一条增广路, 简称为 增广路(或称为增广链、可改进路)。沿着增广路改进可行流的操做称为增广优化
对于一张流量图G,断开一些边后,源点s和汇点t就不在连通,咱们将这样的k条边的权值(即最大容量)和求和,求和后的值称为割。显然,对于一张流量图G,割有不少个且不尽相同。咱们要求的就是全部割中权值最小的那一个(可能不惟一),即花最小的代价使s和t不在同一集合中。spa
FF方法(Ford-Fulkerson)code
根据增广路定理, 为了获得最大流, 能够从任何一个可行流开始, 沿着增广路对网络流进行增广, 直到网络中不存在增广路为止,这样的算法称为增广路算法。问题的关键在于如何有效地找到增广路, 并保证算法在有限次增广后必定终止。
FF方法的基本流程是 :blog
反向弧创建的意义:为程序提供反悔的机会
很明显,上图最大流应该是2,但咱们找到了一条错误的路径,因而咱们就应该有返回的机会,即创建反向边,这样再次从反向边流过就至关于抵消了。队列
在EK算法中, 程序的实现过程与增广路求最大流的过程基本一致. 即每一次更新都进行一次找增广路而后更新路径上的流量的过程。可是咱们能够从上图中发现一个问题, 就是每次找到的增广路曲曲折折很是长, 此时咱们每每走了冤枉路(即:明明咱们能够从源点离汇点越走越近的,但是中间的几条边却向离汇点远的方向走了), 此时更新增广路的复杂度就会增长。EK 算法为了规避这个问题使用了 bfs 来寻找增广路, 而后在寻找增广路的时候老是向离汇点愈来愈近的方向去寻找下一个结点。博客
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #define INF 0x7fffffff #define N 10010 #define M 100010 using namespace std; int n,m,ss,tt; struct Edge{int to;int next;int value;}e[M<<1]; struct Pre{int node;int id;}pre[M<<1];//pre[i].node表示编号为i的点最短路的上一个点,pre[i].id表示最短路上链接i点的边的编号 int head[N],cnt=-1;//编号从0开始,缘由见下 bool vis[N]; queue<int> q; void add(int from,int to,int value) { cnt++; e[cnt].to=to; e[cnt].value=value; e[cnt].next=head[from]; head[from]=cnt; } bool bfs(int s,int t)//用来寻找s,t的最短路并记录,若是s,t不连通则返回0 { q=queue<int>();//清空队列 memset(vis,0,sizeof(vis)); memset(pre,-1,sizeof(pre)); pre[s].node=s; vis[s]=1; q.push(s); while(!q.empty()) { int x=q.front(); q.pop(); for(int i=head[x];i>-1;i=e[i].next) { int now=e[i].to; if(!vis[now]&&e[i].value)//忽略流量为0的边 { pre[now].node=x;//用pre记录最短路 pre[now].id=i; vis[now]=1; if(now==t)return 1;//找到 q.push(now); } } } return 0; } int EK(int s,int t) { int ans=0; while(bfs(s,t)) { int minv=INF; for(int i=t;i!=s;i=pre[i].node) minv=min(minv,e[pre[i].id].value); for(int i=t;i!=s;i=pre[i].node) { e[pre[i].id].value-=minv; e[pre[i].id^1].value+=minv;//x^1表示x边的反向边,此方法仅在边的编号从0开始时有效 } ans+=minv; } return ans; } int main() { memset(head,-1,sizeof(head)); scanf("%d%d%d%d",&n,&m,&ss,&tt); for(int i=1;i<=m;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); add(a,b,c); add(b,a,0);//创建反向边 } printf("%d\n",EK(ss,tt)); return 0; }
其实Dinic算法是EK算法的改进
发如今EK算法中,每增广一次都要先进行bfs寻找最短增广路,然而bfs后,极可能不止一条路径能够增广,若是仍是按照EK算法的bfs一次增广一条路,很显然浪费了不少时间,这样,咱们让bfs负责寻找增广路径,dfs计算可行的最大流。
下图1点为s点,6点为t点,红线表明寻找的路径,蓝线表明回溯的路径:
在普通图中:$\varTheta(n^{2}m)$
在二分图中:$\varTheta(m\sqrt{n})$
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #define N 10010 #define M 100010 #define INF 0x7fffffff using namespace std; int n,m,ss,tt; int dis[N]; queue<int> q; struct Edge{int to;int value;int next;}e[M<<1]; int head[N],cnt=-1; void add(int from,int to,int value) { cnt++; e[cnt].to=to; e[cnt].value=value; e[cnt].next=head[from]; head[from]=cnt; } bool bfs(int s,int t)//bfs功能和EK算法的类似,不一样的是Dinic中的bfs要求出全部点到源点s的最短路dis[i] { q=queue<int>();//清空队列 memset(dis,-1,sizeof(dis)); dis[s]=0; q.push(s); while(!q.empty()) { int x=q.front(); q.pop(); for(int i=head[x];i>-1;i=e[i].next) { int now=e[i].to; if(dis[now]==-1&&e[i].value!=0) { dis[now]=dis[x]+1; q.push(now); } } } return dis[t]!=-1; } int dfs(int x,int t,int maxflow)//表示从x出发寻找到汇点T的增广路,寻找到maxflow流量为止,并相应的增广。返回值为实际增广了多少(由于有可能找不到maxflow流量的增广路) { if(x==t)return maxflow; int ans=0; for(int i=head[x];i>-1;i=e[i].next) { int now=e[i].to; if(dis[now]!=dis[x]+1||e[i].value==0||ans>=maxflow)continue; int f=dfs(now,t,min(e[i].value,maxflow-ans)); e[i].value-=f; e[i^1].value+=f; ans+=f; } return ans; } int Dinic(int s,int t) { int ans=0; while(bfs(s,t)) ans+=dfs(s,t,INF); return ans; } int main() { memset(head,-1,sizeof(head)); scanf("%d%d%d%d",&n,&m,&ss,&tt); for(int i=1;i<=m;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); add(a,b,c); add(b,a,0); } printf("%d\n",Dinic(ss,tt)); return 0; }
咱们知道Dinic算法中的dfs是为了在可行增广路中找到最小容量并进行增广。而找增广路须要遍历每一个点所链接的边,直至找到一条可到达终点的路。若是这一次找到了增广路,下一次在访问到这个点时,上一次已经检查过的边就不用再走一遍了,由于遍历一个点链接的边都是有必定顺序的,上一次访问到这个点已经肯定那几条边是不可行的。因而,咱们用cur[i]
来表示下一次遍历边时应该从那一条开始。
虽然渐进时间复杂度没有发生变化,但实际应用中的确大大下降了Dinic的常数
int cur[N]; int dfs(int x,int t,int maxflow) { if(x==t)return maxflow; int ans=0; for(int i=cur[x];i>-1;i=e[i].next) { int now=e[i].to; if(dis[now]!=dis[x]+1||e[i].value==0||ans>=maxflow)continue; cur[x]=i;//此路可行,记录此路 int f=dfs(now,t,min(e[i].value,maxflow-ans)); e[i].value-=f; e[i^1].value+=f; ans+=f; } return ans; } int Dinic(int s,int t) { int ans=0; while(bfs(s,t)) { memcpy(cur,head,sizeof(head));//初始化 ans+=dfs(s,t,INF); } return ans; }
网络流的优化算法还有ISAP(Improved Shortest Augumenting Path),最高标号预流推动(HLPP)等等,Dinic在通常状况下已经够用了,其余算法自学请移步其余大佬博客喽。