最大流EK算法/DINIC算法学习
之前一直觉得很难,没学过网络流,毕竟是基础知识现在重新来看。
定义一下网络流问题,就是在一幅有向图中,每条边有两个属性,一个是cap表示容量,一个是flow
表示流过的流量。我们要求解的问题就是从S点到T点最多可以跑多少流量。用比较学术的话说,就是 一个有向图 G=(V,E);有两个特别的点:源点s、汇点t;图中每条边(u,v)∈E,有一个非负值的容量C(u,v),流量F(u,v)。
定义一下"残流网络":即当前边还可以流过的流量,也就是cap-flow。
其中,在最大流的问题中,我们要满足三个条件:
1:容量限制:f(u,v)<c(u,v),即每条边流过的流量不能超过容量上限。
2:斜对称性:(f(u,v)=-f(v,u))在后面的运用中,我们叫做一旦有物品从u运到v,那么则肯定有一个可退流从v运到u(这里不懂得稍后解释正确性)。
3:流量平衡:简单来说就是除了源点和汇点,没有其他点可以保存货物。显然f(s,u)==f(v,t)。也就是说非ST节点不累计流量,流进的等于流出的流量。
最大流问题,就是使得f(s,u)=f(v,t)达到最大。
现在讨论如何解决这个问题,有一个很容易想到的贪心思路是:我们暴力的从S点到T点找可行路,一旦找到一条路,总流量就加上这条路径上的MIN{cap-flow} ,因为最小的限制最大的。
比如下图: <c,f>表示当前弧的容量及流过的流量。
如果我们要求A->F的最大流的话,先找到第一条可行路(A->B->C->F),这条路上的最大允许通过的流量是5-2=3,执行之后,A->B这条路上就满流了,也就是不能从A->B在通过流量了。第二次找的时候显然只能找到(A->D->E->F)这条路,最大通过流量是5-3=2 ,第三次就找不到路了。答案就是2+3=5;
这样看来这种方法貌似可行,可惜的是有一点没考虑到,如果我们第一次找到的路影响到后面的流的行走导致最优解发生变化怎么办。程序没有反悔的机会,比如下图:
http://acm.hdu.edu.cn/showproblem.php?pid=1532
Drainage Ditches
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 20435 Accepted Submission(s): 9804
Farmer John knows not only how many gallons of water each ditch can transport per minute but also the exact layout of the ditches, which feed out of the pond and into each other and stream in a potentially complex network.
Given all this information, determine the maximum rate at which water can be transported out of the pond and into the stream. For any given ditch, water flows in only one direction, but there might be a way that water can flow in a circle.
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define inf 0x3f3f3f3f 4 int cap[220][220]; 5 int a[220],p[220]; 6 int n,m; 7 int bfs(int s,int t) 8 { 9 queue<int>q; 10 memset(p,-1,sizeof(p)); 11 q.push(s); 12 a[s]=inf; 13 p[s]=0; 14 while(!q.empty()){ 15 int u=q.front();q.pop(); 16 if(u==t)break; 17 for(int i=1;i<=n;++i) 18 { 19 if(i!=s&&cap[u][i]>0&&p[i]==-1){ 20 p[i]=u; 21 a[i]=min(cap[u][i],a[u]); 22 q.push(i); 23 } 24 } 25 } 26 if(p[t]==-1)return -1; 27 return a[t]; 28 } 29 int maxflow(int s,int t) 30 { 31 int ret=0,delta=0; 32 while((delta=bfs(s,t))!=-1){ 33 int k=t; 34 while(k!=s){ 35 int las=p[k]; 36 cap[las][k]-=delta; 37 cap[k][las]+=delta; 38 k=p[k]; 39 } 40 ret+=delta; 41 } 42 return ret; 43 } 44 int main() 45 { 46 int i,j,k; 47 int u,v,w; 48 while(cin>>m>>n){ 49 memset(cap,0,sizeof(cap)); 50 memset(a,0,sizeof(a)); 51 for(i=1;i<=m;++i){ 52 cin>>u>>v>>w; 53 if(u==v)continue; 54 cap[u][v]+=w; 55 } 56 cout<<maxflow(1,n)<<endl; 57 } 58 return 0; 59 }
贴上一发DINIC算法的,数据不大所以看不出来二者的时间差异。
DINIC算法先跑bfs对点进行分层,然后逐层跑阻塞流来增广。
当前边优化,用一个cur[u]记录u点当前遍历到那一条边了,下一次再访问到u的时候直接从上一次记录的
边开始遍历,减少很多遍历的时间。因为已经访问过的边肯定已经满了(最大化)所以不必要再访问了。
多路增广,当前弧优化,炸点。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define inf 0x3f3f3f3f 4 const int maxn=220; 5 struct Edge 6 { 7 int v,cap,flow,next; 8 }e[maxn<<1]; 9 int first[maxn],d[maxn],cur[maxn],tot,N,M; 10 bool vis[maxn]; 11 void add(int u,int v,int cap,int flow){ 12 e[tot]=Edge{v,cap,flow,first[u]}; 13 first[u]=tot++; 14 } 15 bool bfs(){ 16 memset(vis,0,sizeof(vis)); 17 memset(d,0,sizeof(d)); 18 queue<int>q; 19 q.push(1); 20 d[1]=0; 21 vis[1]=1; 22 while(!q.empty()){ 23 int u=q.front(); 24 q.pop(); 25 for(int i=first[u];~i;i=e[i].next){ 26 if(!vis[e[i].v]&&e[i].cap>e[i].flow){ 27 vis[e[i].v]=1; 28 d[e[i].v]=d[u]+1; 29 q.push(e[i].v); 30 } 31 } 32 } 33 return vis[N]; 34 35 } 36 int dfs(int u,int a){ 37 if(u==N||a==0) return a; 38 int flow=0,f; 39 for(int &i=cur[u];~i;i=e[i].next){ 40 if(d[e[i].v]==d[u]+1 && (f=dfs(e[i].v,min(a,e[i].cap-e[i].flow)))>0){ 41 e[i].flow+=f; 42 e[i^1].flow-=f; 43 flow+=f; 44 a-=f; 45 if(!a) break; 46 } 47 } 48 return flow; 49 } 50 void solve(){ 51 int ans=0; 52 while(bfs()){ 53 for(int i=1;i<=N;++i) cur[i]=first[i]; 54 ans+=dfs(1,inf); 55 } 56 printf("%d\n",ans); 57 } 58 int main(){ 59 while(scanf("%d%d",&M,&N)!=EOF){ 60 tot=0; 61 memset(first,-1,sizeof(first)); 62 int u,v,w; 63 while(M--){ 64 scanf("%d%d%d",&u,&v,&w); 65 add(u,v,w,0); 66 add(v,u,0,0); 67 } 68 solve(); 69 } 70 return 0; 71 } 72 /* 73 5 4 74 1 2 40 75 1 4 20 76 2 4 20 77 2 3 30 78 3 4 10 79 */