网络流初步--最大流
网络流 (Network Flow) 是指在一个每条边都有容量 (Capacity) 的有向图分配流,使一条边的流量不会超过它的容量。(边有附带容量的图称为网络)一道流必须符合一个结点的进出的流量相同的限制,除非这是一个源点 (Source) ──有较多向外的流;或是一个汇点 (Sink) ──有较多向内的流。一个网络可以用来模拟道路系统的交通量、管中的液体、电路中的电流或类似一些东西在一个结点的网络中游动的任何事物。
设 是一个有限的有向图,它的每条边 都有一个非负值实数的容量 。如果 ,我们假设 。我们区别两个顶点:一个源点 和一个汇点 。一道网络流是一个对于所有结点 和 都有以下特性的实数函数 :
容量限制 (Capacity Constraints): | 一条边的流不能超过它的容量。 |
斜对称 (Skew Symmetry): | 由 到 的净流必须是由 到 的净流的相反(参考例子)。 |
流守恒 (Flow Conservation): | 除非或,否则 一结点的净流是零,除了“制造”流的源点和“消耗”流的汇点。 |
边的剩余容量 (Residual Capacity) 是 。这定义了以 表示的剩余网络 (Residual Network),它显示可用的容量的多少。留意就算在原网络中由 到 没有边,在剩余网络仍可能有由 到 的边。因为相反方向的流抵消,减少由 到 的流等于增加由 到 的流。增广路 (Augmenting Path) 是一条路径 ,而 、及,这表示沿这条路径传送更多流是可能的(既找一条从s到t的路径,路径上边的剩余容量都不为0)。
【增广路求最大流算法】
在网络中找到一条增广路,路径上剩余容量最小的为d,把路径上所有边的流量都+d,显然操作后依旧满足上面的3条性质。只要网络中存在增广路,网络的流量就可以增加。如果不存在增广路,则当前流是最大流。
【Edmonds-Karp】
这个算法写的比较简洁,是用BFS来找增广路径的,防止一些数据会卡DFS。
算法复杂度为O(VE2),适合节点多边少的网络,如果要求解边多的网络,则要用Dinic算法: O(V2E)
1 /* 2 数组a[i]表示从路径起点到i点的最小剩余量 3 数组p[u]记录路径中到达u点的前一个点 4 f[u][v]表示从u到v的实际流量,要保证当前网络中的流量满足3个性质才能执行算法,最好都清0吧。 5 */ 6 queue<int> q; 7 int sum=0; 8 while (true) 9 { 10 memset(a,0,sizeof a); 11 a[s]=MAXINT; //s为起点,初始化防止被更新 12 q.push(s); 13 while (!q.empty()) 14 { 15 int u=q.front();q.pop(); 16 for (int v=1;v<=n;++v) 17 if (!a[v] && c[u][v]>f[u][v]) 18 { 19 p[v]=u; q.push(v); 20 int t=c[u][v]-f[u][v]; 21 a[v]=a[u]<t?a[u]:t; 22 } 23 } 24 if (a[t]==0) break; 25 for (int u=t;u!=s;u=p[u]) 26 { 27 f[p[u]][u]+=a[t]; 28 f[u][p[u]]-=a[t]; 29 } 30 sum+=a[t]; //更新网络总流量 31 }
代码中的BFS部分有点意思,由于数组a总是正数(c[u][v]>f[u][v]),所以可以当做标记是否该点被访问过。BFS中所有点只被更新一次,如果第一次访问该点无法到达t,则再多次访问也没用,所以效率还是蛮高的。
【HDU 3549】
最大流的最基本题。
1 #include <stdio.h> 2 #include <string.h> 3 #include <queue> 4 using namespace std; 5 int n,m; 6 int c[20][20]; 7 int f[20][20]; 8 int work() 9 { 10 int p[50]; 11 queue<int> q; 12 int sum=0; 13 int a[20]; 14 while (true) 15 { 16 memset(a,0,sizeof a); 17 a[1]=2000000; 18 q.push(1); 19 while (!q.empty()) 20 { 21 int u=q.front();q.pop(); 22 for (int v=1;v<=n;++v) 23 if (!a[v] && c[u][v]>f[u][v]) 24 { 25 p[v]=u;q.push(v); 26 int t=c[u][v]-f[u][v]; 27 a[v]=a[u]<t?a[u]:t; 28 } 29 } 30 if (a[n]==0) break; 31 for (int u=n;u!=1;u=p[u]) 32 { 33 f[p[u]][u]+=a[n]; 34 f[u][p[u]]-=a[n]; 35 } 36 sum+=a[n]; 37 } 38 return sum; 39 } 40 int main() 41 { 42 int T; 43 int cas=0; 44 scanf("%d",&T); 45 while (T--) 46 { 47 memset(c,0,sizeof c); 48 memset(f,0,sizeof f); 49 scanf("%d%d",&n,&m); 50 for (int i=0;i<m;++i) 51 { 52 int x,y,t; 53 scanf("%d%d%d",&x,&y,&t); 54 c[x][y]+=t; 55 } 56 printf("Case %d: %d\n",++cas,work()); 57 } 58 }
用最大流模板的代码:
1 #include <stdio.h> 2 #include <string.h> 3 #include <queue> 4 using namespace std; 5 int n,m; 6 class nstream 7 { 8 static const int MAXINT=0x7fffffff; 9 struct node 10 { 11 int n,c,f; 12 int next; 13 void set(int nn,int cc,int xx) 14 { 15 n=nn; c=cc; 16 next=xx; f=0; 17 } 18 } *data; 19 int *head,cnt,n,m; 20 int *getf(int x,int y) 21 { 22 int ind=head[x]; 23 for (node *t; ind!=-1; ind=t->next) 24 { 25 t=&data[ind]; 26 if (t->n==y) return &t->f; 27 } 28 insert(x,y,0); 29 return &data[head[x]].f; 30 } 31 public: 32 nstream(int nn,int mm):n(nn),m(mm) //输入最大节点数和边数 33 { 34 data=new node[mm]; 35 head=new int[nn+2]; 36 } 37 void clear() //清理数据,初始化 38 { 39 for (int i=0;i<=n;++i) head[i]=-1; 40 cnt=-1; 41 } 42 void insert(int x,int y,int k) // 把x到y的容量加k 43 { 44 int ind=head[x]; 45 for (node *t; ind!=-1; ind=t->next) 46 { 47 t=&data[ind]; 48 if (t->n==y) 49 { 50 t->c+=k; 51 return; 52 } 53 } 54 data[++cnt].set(y,k,head[x]); 55 head[x]=cnt; 56 } 57 int work(int s,int t) //计算从s 到t 的最大流 58 { 59 int p[50]; 60 queue<int> q; 61 int sum=0; 62 int a[25]; 63 while (true) 64 { 65 memset(a,0,sizeof a); 66 a[s]=MAXINT; 67 q.push(s); 68 while (!q.empty()) 69 { 70 int u=q.front(); 71 q.pop(); 72 node *t; 73 for (int pp=head[u]; pp!=-1; pp=t->next) 74 { 75 t=&data[pp]; 76 int v=t->n; 77 if (!a[v] && t->c>t->f) 78 { 79 p[v]=u; 80 q.push(v); 81 int tt=t->c-t->f; 82 a[v]=a[u]<tt?a[u]:tt; 83 } 84 } 85 } 86 if (a[t]==0) break; 87 for (int u=t; u!=s; u=p[u]) 88 { 89 (*getf(p[u],u))+=a[t]; 90 (*getf(u,p[u]))-=a[t]; 91 } 92 sum+=a[t]; 93 } 94 return sum; 95 } 96 }; 97 98 int main() 99 { 100 int T; 101 int cas=0; 102 scanf("%d",&T); 103 nstream my(20,20*23); 104 while (T--) 105 { 106 my.clear(); 107 scanf("%d%d",&n,&m); 108 for (int i=0; i<m; ++i) 109 { 110 int x,y,t; 111 scanf("%d%d%d",&x,&y,&t); 112 my.insert(x,y,t); 113 } 114 printf("Case %d: %d\n",++cas,my.work(1,n)); 115 } 116 }