首先来认识一下网络流中最大流的问题
给定一个有向图G=(V,E),把图中的边看做成管道,边权看做成每根管道能通过的最大流量(容量),给定源点s和汇点t,在源点有一个水源,在汇点有一个蓄水池,问s-t的最大水流量是多少
网络流图里,源点流出的量等于汇点流入的量,除源汇外的任何点,其流入量之和等于流出量之和 。
首先我们来看下面的图
s是源点,t是汇点
先这么想,先用dfs找出一条从s-t的路线,把他塞满,然后流量就是路径中容量最小的那条路的容量,然后把路径上的容量都剪去这个流量,再重新从s-t找可行路径,直到找不到为止
用这种思路看这个图
先走S-A-B-T,这样流量为100,并且没有可行路径了,即操作结束.
可是很明显,从S-A-T,S-B-T这两条路加起来的流量为200。所以这种思路是错的。
主要是过早的认为A-B的流量不为0
改进的思路:建立一个可以修改的网络,使得不合理的流可以被删掉
一种实现:对上次dfs时找到的流量路径上的边,添加一条“反向”边,反向边上的容量等于上次dfs时找到的该边上的流量,然后再利用“反向”的容量和其他边上剩余的容量寻找路径。
使用这种思路再求一次
第一次dfs后
第二次dfs(为了方便把容量为0的边删了)
这个时候已经没有可以走的边了,流量为200,dfs结束
为什么这种思路是正确的呢,网上有不少详细的证明。
Ford-Fulkerson算法
就是用这种思路做的
用dfs求增广路径,每次找到之后处理,直到找不到为止。
假设有n个定点,m条边,那么dfs的复杂度为n+m;
dfs运行c次
所以复杂度为c*(n+m);
但是dfs可能会运行很多次。
比如上面的图如果A-B中有条容量为1的边,那么运气不好的话,能执行200次dfs;
但实际上只要用2次就能找到
在每次增广的时候,选择从源到汇的具有最少边数的增广路径,即不是通过dfs寻找增广路径,而是通过bfs寻找增广路径。
这就是Edmonds-Karp 最短增广路算法
已经证明这种算法的复杂度上限为nm2 (n是点数, m是边数);
现在来说几道题目
1-〉POJ 1273
题意:网络流的裸题,1为源点,n为汇点,给定每条边的容量,求最大流,用EK算法
1273 | Accepted | 1052K | 0MS | G++ | 1430B |
1 #include <stdio.h> 2 #include <iostream> 3 #include <stdlib.h> 4 #include <string.h> 5 #include <algorithm> 6 #include <vector> 7 #include <queue> 8 using namespace std; 9 #define N 300 10 #define INF 0x7fffffff 11 int Map[N][N]; 12 int path[N]; 13 //bool vis[N]; 14 int n,m; 15 bool bfs(int s,int t) 16 { 17 int p; 18 queue<int> q; 19 memset(path,-1,sizeof(path)); 20 //memset(vis,false,sizeof(vis)); 21 path[s]=s; 22 // vis[s]=true; 23 q.push(s); 24 while(!q.empty()) 25 { 26 p=q.front(); 27 q.pop(); 28 for(int i=1;i<=n;i++) 29 { 30 if(Map[p][i]>0&&path[i]==-1) 31 { 32 path[i]=p; 33 //vis[i]=true; 34 if(i==t) 35 return true; 36 q.push(i); 37 } 38 } 39 } 40 return false; 41 } 42 int EK(int s,int t) 43 { 44 int flow=0; 45 int d; 46 int i; 47 while(bfs(s,t)) 48 { 49 d=INF; 50 for(i=t;i!=s;i=path[i]) 51 { 52 d=min(d,Map[path[i]][i]); 53 } 54 for(i=t;i!=s;i=path[i]) 55 { 56 Map[path[i]][i]-=d; 57 Map[i][path[i]]+=d; 58 } 59 flow+=d; 60 } 61 return flow; 62 } 63 int main() 64 { 65 while(scanf("%d %d",&m,&n)!=EOF) 66 { 67 memset(Map,0,sizeof(Map)); 68 for(int i=1;i<=m;i++) 69 { 70 int from,to,flow; 71 scanf("%d %d %d",&from,&to,&flow); 72 Map[from][to]+=flow; 73 } 74 printf("%d ",EK(1,n)); 75 } 76 77 return 0; 78 }
2-〉POJ 3436
题意:一台电脑有P个部分,当电脑所有部分都被修好的时候,这台电脑才能出厂,有N台机器,每台机器每天最多能处理Q台电脑,机器只能接收与要求相符合的电脑,0表示这个部件不能有,1表示这个部件必须有,2表示这个部件可有可无,机器接受电脑部件之后会产出相应的产品,1表示这个部件有,0表示这个部件没有。求工厂一天能出厂多少台电脑。
思路:拆点建图,把接收形如222,000。。。(只要其中没有1),就把源点向这个点连一条容量为无穷大的边,把产出为111的,就把这个点向汇点连一条无穷大的边,我把编号为i的点,那么这个点拆成2*i-1和2*i两个点,2*i-1代表接受的,2*i代表产出的,这两个点之间连一条容量为第i台机器每天处理的电脑量的边,如果某台机器产出的点符合令一台机器接受的点,那就把那两个点也连上一条容量为无穷大的边。之后求最大流就可以了。
3436 | Accepted | 8648K | 32MS | G++ | 3338B |
1 #include <stdio.h> 2 #include <string.h> 3 #include <iostream> 4 #include <queue> 5 #include <stdlib.h> 6 #include <stack> 7 using namespace std; 8 #define N 1000 9 #define INF 0x7fffffff 10 int pre[N]; 11 int map[N][N]; 12 int mmap[N][N]; 13 int P,n; 14 struct node 15 { 16 int rec[N]; 17 int pro[N]; 18 int flow; 19 }; 20 node mac[N]; 21 bool bfs(int s,int t) 22 { 23 int p; 24 stack<int> q;//不知道为什么stack能过queue就wa了。。 25 memset(pre,-1,sizeof(pre)); 26 pre[s]=s; 27 q.push(s); 28 while(!q.empty()) 29 { 30 p=q.top(); 31 q.pop(); 32 for(int i=0;i<=2*n+1;i++) 33 { 34 if(map[p][i]>0&&pre[i]==-1) 35 { 36 pre[i]=p; 37 if(i==t) 38 return true; 39 q.push(i); 40 41 } 42 } 43 } 44 return false; 45 } 46 void EK(int s,int t) 47 { 48 int flow=0; 49 int d,i; 50 int cnt=0; 51 while(bfs(s,t)) 52 { 53 d=INF; 54 for(i=t;i!=s;i=pre[i]) 55 d=min(d,map[pre[i]][i]); 56 for(i=t;i!=s;i=pre[i]) 57 { 58 map[pre[i]][i]-=d; 59 if(!mmap[pre[i]][i]) 60 { 61 if(pre[i]%2==0&&i&1&&i!=t&&pre[i]!=0) 62 { 63 64 cnt++; 65 } 66 } 67 map[i][pre[i]]+=d; 68 mmap[pre[i]][i]+=d;//每台机器之间流过的电脑数量 69 } 70 71 flow+=d; 72 } 73 printf("%d %d ",flow,cnt);//最大流就是最多能产出的电脑,cnt就是几条机器之间的路径 74 for(int i=1;i<=2*n;i++) 75 for(int j=1;j<=2*n;j++) 76 { 77 if(mmap[i][j]&&i%2==0&&j%2!=0) 78 { 79 printf("%d %d %d ",i/2,(j+1)/2,mmap[i][j]); 80 } 81 } 82 } 83 int main() 84 { 85 while(scanf("%d %d",&P,&n)!=EOF) 86 { 87 int cnt=1; 88 memset(map,0,sizeof(map)); 89 memset(mmap,0,sizeof(mmap)); 90 for(int i=1;i<=n;i++) 91 { 92 scanf("%d",&mac[i].flow); 93 for(int j=1;j<=P;j++)scanf("%d",&mac[i].rec[j]); 94 for(int j=1;j<=P;j++)scanf("%d",&mac[i].pro[j]); 95 map[cnt][cnt+1]=mac[i].flow;//拆点 96 cnt+=2; 97 } 98 bool flag; 99 for(int i=1;i<=n;i++)//处理源点和汇点 100 { 101 bool flag1=true; 102 bool flag2=true; 103 for(int j=1;j<=P;j++) 104 if(mac[i].pro[j]==0)flag2=false; 105 if(flag2) 106 map[i*2][2*n+1]=INF; 107 flag1=true; 108 flag2=true; 109 for(int j=1;j<=P;j++) 110 if(mac[i].rec[j]==1)flag1=false; 111 if(flag1) 112 map[0][2*i-1]=INF; 113 } 114 for(int i=1;i<=n;i++)//每台机器之间连边 115 { 116 for(int j=1;j<=n;j++) 117 { 118 if(i==j) 119 continue; 120 for(int k=1;k<=P;k++) 121 { 122 flag=true; 123 if((mac[i].pro[k]==1&&mac[j].rec[k]==0)||(mac[i].pro[k]==0&&mac[j].rec[k]==1)) 124 { 125 flag=false; 126 break; 127 } 128 } 129 if(flag) 130 { 131 int u=i*2; 132 int v=j*2-1; 133 map[u][v]=INF; 134 } 135 } 136 } 137 138 /* for(int i=0;i<=2*n+1;i++) 139 { 140 for(int j=0;j<=2*n+1;j++) 141 { 142 printf("i:%d j:%d map[i][j]=%d ",i,j,map[i][j]); 143 } 144 }*/ 145 EK(0,2*n+1); 146 } 147 return 0; 148 }
3-> POJ 2112
题意:有k个挤奶机编号为1-k,有c个奶牛编号为k+1-k+c,奶牛和挤奶机之间有路径(没有路径给出来的距离是0),一个挤奶机每天可以处理M头奶牛
求出需要走最大距离去挤奶的牛的路径最小值
思路:先用floyd求出每个牛到每个挤奶机的最小路径,问题就转化成了已知每个奶牛到每个挤奶机的路径,一个奶牛只能去一台机器,每台机器每天可以处理M头牛,求需要走最大距离去挤奶的牛的路径的最小值,用网络流+二分答案,先假设最远的距离是求floyd时所求得的最短路径中的最大值,把源点和每头牛之间连容量为1的一条边,把每个挤奶器与汇点连容量为M的一条边,每头牛到挤奶机的距离如果小于或者等于dismax说明可以连接一条容量为1的边,然后求最大流,如果最大流等于C即牛的数量的时候,二分答案,直到求出答案为止。
2112 | Accepted | 5528K | 750MS | G++ | 2883B |
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <iostream> 5 #include <algorithm> 6 #include <queue> 7 #include <stack> 8 #define N 1000 9 #define INF 999999999 10 using namespace std; 11 int mmap[N][N]; 12 int n; 13 int k,c,m; 14 int map[N][N]; 15 int pre[N]; 16 int maxn; 17 int dismaxn; 18 void floyd() 19 { 20 21 maxn=-1; 22 //for(int i=k+1;i<=n;i++) 23 // for(int j=1;j<=k;j++) 24 //cout<<"i:"<<i<<" j:"<<j<<" mmap[i][j]:"<<mmap[i][j]<<endl; 25 for(int k=1;k<=n;k++) 26 { 27 for(int i=1;i<=n;i++) 28 { 29 for(int j=1;j<=n;j++) 30 { 31 mmap[i][j]=min(mmap[i][j],mmap[i][k]+mmap[k][j]);//保留原图 32 33 } 34 } 35 } 36 for(int i=k+1;i<=n;i++) 37 for(int j=1;j<=k;j++) 38 maxn=max(maxn,mmap[i][j]);//二分答案的上界 39 40 dismaxn=maxn; 41 } 42 void build(int dismax) 43 { 44 memset(map,0,sizeof(map)); 45 46 for(int i=k+1;i<=n;i++) 47 { 48 for(int j=1;j<=k;j++) 49 { 50 51 if(mmap[i][j]<=dismax) 52 { 53 map[i][j]=1;//建边 54 55 } 56 } 57 } 58 for(int i=k+1;i<=n;i++) 59 { 60 map[0][i]=1; 61 62 } 63 for(int i=1;i<=k;i++) 64 { 65 map[i][n+1]=m; 66 67 } 68 } 69 bool bfs(int s,int t)//寻找增广路径 70 { 71 int p; 72 queue<int> q; 73 memset(pre,-1,sizeof(pre)); 74 pre[s]=s; 75 q.push(s); 76 while(!q.empty()) 77 { 78 p=q.front(); 79 q.pop(); 80 for(int i=1;i<=n+1;i++) 81 { 82 83 if(map[p][i]>0&&pre[i]==-1) 84 { 85 pre[i]=p; 86 if(i==t) 87 return true; 88 q.push(i); 89 } 90 } 91 } 92 return false; 93 } 94 bool ek(int s,int t) 95 { 96 int flow=0,d,i; 97 98 while(bfs(s,t)) 99 { 100 d=INF; 101 for(i=t;i!=s;i=pre[i]) 102 d=min(d,map[pre[i]][i]); 103 for(i=t;i!=s;i=pre[i]) 104 { 105 map[pre[i]][i]-=d; 106 map[i][pre[i]]+=d; 107 } 108 flow+=d; 109 } 110 if(flow==c) 111 return true; 112 return false; 113 } 114 void bound() 115 { 116 int ub=maxn; 117 int lb=0; 118 while(ub-lb>1) 119 { 120 int mid=(lb+ub)/2; 121 build(mid); 122 if(ek(0,n+1)) 123 { 124 ub=mid; 125 } 126 else lb=mid; 127 } 128 printf("%d ",ub); 129 } 130 int main() 131 { 132 while(scanf("%d %d %d",&k,&c,&m)!=EOF) 133 { 134 n=k+c; 135 for(int i=1;i<=n;i++) 136 { 137 for(int j=1;j<=n;j++) 138 { 139 scanf("%d",&mmap[i][j]); 140 if(mmap[i][j]==0) 141 mmap[i][j]=INF; 142 } 143 } 144 145 floyd(); 146 bound(); 147 } 148 return 0; 149 }
4->POJ 1149
题意:有M个锁着的猪圈,每个猪圈有对应的猪的数量,每个猪圈能容纳无穷多的猪,Mirko没有猪圈的钥匙,顾客一个接着一个去农场买猪,每个顾客都有相应猪圈的钥匙和想买猪的数量,每次顾客打开猪圈之后,打开的猪圈里面的猪可以去任意打开的猪圈,要求出来每天能卖的最多的猪
思路:这题的建图有点麻烦,先按照样例建一个图
就算知道这题是求最大流的题目但是拿着这个图也写不了吧。。。
但是图是可以简化的
点的合并有这些规律
规律 1. 如果几个节点的流量的来源完全相同,则可以合并成一个
规律 2. 如果几个节点的流量的去向完全相同, 则可以把它们合并成一个。
规律 3. 如果从点 u 到点 v 有一条容量为 +∞ 的边,并且 u 是 v 的唯一流量来源,或者 v 是 u 的唯一流量去向,则可以把 u 和 v 合并成一个 节点。
简化完之后的图
其实可以这么想这个图,每个猪圈的第一个顾客,就把这个猪圈就把源点向顾客连一条边,边的容量就是猪圈里面猪的数量,因为他是猪圈的第一个顾客,所以初始猪圈里面的猪他都能买,他打开了猪圈M之后,可能他打开的猪圈里面所有的猪都置换进了M里面,而M里面的猪可能又会被M的下一位顾客买走,所以对每个猪圈里面的1-n个用户,把i->i+1连一条容量为无穷大的边
所以每次如果想不到什么好的构图方法的话,可以先根据样例把图画出来,然后利用简化的规则,把图简化一下,说不定就有建图的思路了。
1149 | Accepted | 1388K | 47MS | G++ | 2198B |
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <iostream> 5 #include <algorithm> 6 #include <queue> 7 #include <vector> 8 #include <stack> 9 #define N 200 10 #define INF 999999999 11 using namespace std; 12 int n,m; 13 struct node 14 { 15 int key[2000]; 16 int cnt; 17 int need; 18 }; 19 node peo[N]; 20 int pre[N*N]; 21 int map[N][N]; 22 int pig[N*10]; 23 bool vis[N*10]; 24 vector<int> user[N*10]; 25 void build()//建图 26 { 27 for(int i=1;i<=m;i++) 28 { 29 for(int j=0;j<user[i].size()-1;j++) 30 { 31 map[user[i][j]][user[i][j+1]]=INF; 32 } 33 } 34 35 } 36 bool bfs(int s,int t) 37 { 38 memset(pre,-1,sizeof(pre)); 39 /* for(int i=0;i<=n+1;i++) 40 for(int j=0;j<=n+1;j++) 41 cout<<"i:"<<i<<"j:"<<j<<"map[i][j] "<<map[i][j]<<endl;*/ 42 queue<int> q; 43 q.push(s); 44 pre[s]=s; 45 while(!q.empty()) 46 { 47 int p=q.front(); 48 q.pop(); 49 for(int i=1;i<=n+1;i++) 50 { 51 if(map[p][i]>0&&pre[i]==-1) 52 { 53 pre[i]=p; 54 if(i==t) 55 return true; 56 q.push(i); 57 } 58 59 } 60 } 61 return false; 62 } 63 int EK(int s,int t) 64 { 65 int d,flow=0; 66 while(bfs(s,t)) 67 { 68 d=INF; 69 for(int i=t;i!=s;i=pre[i]) 70 d=min(d,map[pre[i]][i]); 71 for(int i=t;i!=s;i=pre[i]) 72 { 73 map[pre[i]][i]-=d; 74 map[i][pre[i]]+=d; 75 76 } 77 flow+=d; 78 } 79 return flow; 80 } 81 int main() 82 { 83 while(scanf("%d %d",&m,&n)!=EOF) 84 { 85 for(int i=1;i<=m;i++) 86 scanf("%d",&pig[i]); 87 memset(vis,false,sizeof(vis)); 88 for(int i=1;i<=n;i++) 89 { 90 scanf("%d",&peo[i].cnt); 91 for(int j=1;j<=peo[i].cnt;j++) 92 { 93 scanf("%d",&peo[i].key[j]); 94 user[peo[i].key[j]].push_back(i);//每个猪圈按照顺序把顾客编号放进去 95 if(!vis[peo[i].key[j]])//给第一个顾客连线 96 { 97 map[0][i]+=pig[peo[i].key[j]]; 98 vis[peo[i].key[j]]=true; 99 } 100 } 101 scanf("%d",&peo[i].need); 102 map[i][n+1]=peo[i].need;//顾客和汇点连线 103 } 104 build();//建图 105 printf("%d ",EK(0,n+1)); 106 } 107 return 0; 108 }