网络流算法之EK
最基础的网络流算法
不停地找增广路进行增广,直到无法增广为止
时间复杂度O(VE^2)
#include<iostream> #include<cstdio> #include<queue> #include<cstring> using namespace std; int maxdata=0x7fffffff; int capacity[200][200],c[1000][1000];//c[i][j]保存初值,因为每次计算都会改变capacity[i][j]的值,capacity[i][j]表示残留网络的容量 int flow[200];//标记从源点到当前点实际还有多少容量可用 int pre[200];//标记在这条路径上当前节点的前驱,同时标记该节点是否在队列中 int x[1000],y[10000],len[10000];//x,y,len存储每条边的信息 int n,m,p; //n 表示边数,m表示节点数,p表示操作数 queue<int> myqueue; int bfs(int src,int des) { int i,j; while(!myqueue.empty())//队列清空 myqueue.pop(); for (i=1;i<m+1;i++) pre[i]=-1;//pre[i]=-1表示该点没有入队 pre[src]=0; flow[src]=maxdata; myqueue.push(src); while (!myqueue.empty()) { int index=myqueue.front(); myqueue.pop(); if (index==des)//index==des说明所有到终点的路已经都搜索完了 break; for (i=1;i<m+1;i++) { if (i!=src&&capacity[index][i]>0&&pre[i]==-1)//i不是源点,且从当前点可以到达i点,i点不在队列中,则i点入队 { pre[i]=index;//该路径上i的前驱点为index flow[i]=min(capacity[index][i],flow[index]);//要通过必须满足流量小于等于所有边的最小值 myqueue.push(i); } } } if (pre[des]==-1) return -1; else return flow[des]; } int maxflow(int src,int des)//src表示源点,des表示汇点 { int increasement=0; int sumflow=0; while((increasement=bfs(src,des))!=-1)//有可以流到汇点的路径 { int k=des; while (k!=src) { int last=pre[k]; capacity[last][k]-=increasement;//用路径上每一条边的容量减去该路径的流量 capacity[k][last]+=increasement;//给程序反悔和改正的机会 k=last; } sumflow+=increasement; } return sumflow; } int main() { int i,j,l; int start,end,ci; cin>>m>>n; memset(capacity,0,sizeof(capacity)); memset(flow,0,sizeof(flow)); for (i=1;i<=n;i++) { cin>>start>>end>>ci; x[i]=start; y[i]=end; len[i]=ci; if (start==end) continue; c[start][end]=capacity[start][end]+=ci; c[end][start]=capacity[end][start]+=ci; } scanf("%d",&p); for (l=1;l<=p;l++) { int x1,y1,z1; for (i=1;i<=m;i++) for (j=1;j<=m;j++) capacity[i][j]=c[i][j]; scanf("%d%d%d",&x1,&y1,&z1); if (x1==0) printf("%d\n",maxflow(y1,z1)); if (x1==1)//改变编号Y1的边的容量, { c[x[y1]][y[y1]]=capacity[x[y1]][y[y1]]=z1; c[y[y1]][x[y1]]=capacity[y[y1]][x[y1]]=z1; } } }//如果把网络流的图看作一系列的管道,那么求最大流就相当于从源点处注水,求最多最终有多少水可以流到汇点
网络流算法之dinic(适用于二分图,二分图中O(SQRT(V)E))
O(V^2E)
Dinic算法算是Ek算法的进阶版,在寻找增广路的基础上,增加了分层图的概念。
• 什么是分层图?
• 顾名思义,根据从S到每个点的远近不同,将�图分成若干层。
• Dinic算法即在寻找增广路的基础上,要求增广路上的每个点,属于分层图上不同的层。
多路增广
• 每次不是寻找一条增广路,而是在DFS中,只要可以就递归增广下去,实际上形成了一张增广网。
当前弧优化
• 对于每一个点,都记录上一次检查到哪一条边。因为我们每次增广一定是彻底增广(即这条已经被增广过的边已经发挥出了它全部的潜力,不可能再被增广了),下一次就不必再检查它,而直接看第一个未被检查的边
// Codevs 1993 #include <cstdio> #include <cstring> #include <cmath> #include <iostream> #include <queue> using namespace std; const int max_n = 220; const int max_m = 220; const int max_e = max_m * 2; const int inf = 1e9; int point[max_n], nxt[max_e], v[max_e], remain[max_e], tot; int cur[max_n], deep[max_n], n, m;//deep记录分层,cur记录检查到哪一条边(当前弧优化) inline int getnum() { char c; int ans = 0; while ((c = getchar()) == ' ' || c == '\n' || c == '\r'); ans = c - '0'; while (isdigit(c = getchar())) ans = ans * 10 + c - '0'; return ans; } inline void addedge(int x, int y, int cap) { tot++; nxt[tot] = point[x]; point[x] = tot; v[tot] = y; remain[tot] = cap; tot++; nxt[tot] = point[y]; point[y] = tot; v[tot] = x; remain[tot] = 0; } inline bool bfs(int s, int t) //排除容量已经为0的边,进行分层 { memset(deep, 0x7f, sizeof(deep)); for (int i = 1; i <= n; i++) cur[i] = point[i]; deep[s] = 0; queue<int> q; q.push(s); while (!q.empty()) { int now = q.front(); q.pop(); for (int tmp = point[now]; tmp != -1; tmp = nxt[tmp])//注意tmp!=-1,因为从0开始存储 if (deep[v[tmp]] > inf && remain[tmp]) deep[v[tmp]] = deep[now] + 1, q.push(v[tmp]); } return deep[t] < inf; } int dfs(int now, int t, int limit) //寻找增广路,实际上应该是一个增广网,limit表示当前路径的源点到现在最窄的(剩余流量最小)的边的剩余流量 { if (!limit || now == t) return limit; int flow = 0, f; for (int tmp = cur[now]; tmp != -1; tmp = nxt[tmp]) { cur[now] = tmp; if (deep[v[tmp]] == deep[now] + 1 && (f = dfs(v[tmp], t, min(limit, remain[tmp])))) { flow += f; limit -= f; remain[tmp] -= f; remain[tmp ^ 1] += f; if (!limit) break; } } return flow; } inline int dinic(int s, int t) { int ans = 0; while (bfs(s, t)) ans += dfs(s, t, inf); return ans; } int main() { tot = -1; memset(point, -1, sizeof(point)); memset(nxt, -1, sizeof(nxt)); m = getnum(); n = getnum(); for (int i = 1; i <= m; i++) { int x = getnum(); int y = getnum(); int cap = getnum(); addedge(x, y, cap); } cout << dinic(1, n) << endl; }
网络流算法之isap
ISAP( Improved Shortest Augmenting Path)也是基于分层思想的最大流算法。所不同的是,它省去了Dinic每次增广后需要重新构建分层图的麻烦,而是在每次增广完成后自动更新每个点的『 标号』 (也就是所在的层)
// Codevs 1993 #include <cstdio> #include <cstring> #include <cmath> #include <iostream> #include <queue> using namespace std; const int max_n = 220; const int max_m = 220; const int max_e = max_m * 2; const int inf = 1e9; int point[max_n], nxt[max_e], v[max_e], remain[max_e], tot; int deep[max_n], num[max_n], cur[max_n], lastedge[max_n];//deep记录分层信息,num记录每一层的点数,lastedge记录前驱节点 bool vis[max_n]; int n, m; inline int getnum() //快速读入 { char c; int ans = 0; while ((c = getchar()) == ' ' || c == '\n' || c == '\r'); ans = c - '0'; while (isdigit(c = getchar())) ans = ans * 10 + c - '0'; return ans; } inline void addedge(int x, int y, int cap) //处理边的信息,注意从0开始存 { tot++; nxt[tot] = point[x]; point[x] = tot; v[tot] = y; remain[tot] = cap; tot++; nxt[tot] = point[y]; point[y] = tot; v[tot] = x; remain[tot] = 0; } inline int addflow(int s, int t) { int ans = inf, now = t; while (now != s) { ans = min(ans, remain[lastedge[now]]); now = v[lastedge[now] ^ 1]; } now = t; while (now != s) { remain[lastedge[now]] -= ans; remain[lastedge[now] ^ 1] += ans; now = v[lastedge[now] ^ 1];//因为isap是从汇点扩展的 } return ans; } inline void bfs(int t) { for (int i = 1; i <= n; i++) deep[i] = n; memset(vis, 0, sizeof(vis)); queue<int> q; deep[t] = 0; q.push(t); vis[t] = true; while (!q.empty()) { int now = q.front(); q.pop(); for (int tmp = point[now]; tmp != -1; tmp = nxt[tmp]) if (!vis[v[tmp]] && remain[tmp ^ 1]) //remain[tmp^1]求逆向边 { vis[v[tmp]] = true; deep[v[tmp]] = deep[now] + 1; q.push(v[tmp]); } } } inline int isap(int s, int t) { int ans = 0, now = s; bfs(t); for (int i = 1; i <= n; i++) num[deep[i]]++; for (int i = 1; i <= n; i++) cur[i] = point[i];//cur 当前弧优化,记录当前检查到哪一条边 while (deep[s] < n) { if (now == t) { ans += addflow(s, t); now = s; } bool has_find = false; for (int tmp = cur[now]; tmp != -1; tmp = nxt[tmp]) { int u = v[tmp]; if (deep[u] + 1 == deep[now] && remain[tmp]) { has_find = true; cur[now] = tmp; lastedge[u] = tmp; now = u; break; } } if (!has_find) {//没有找到增广路,对now当前点重新标号 int minn = n - 1; for (int tmp = point[now]; tmp != -1; tmp = nxt[tmp]) if (remain[tmp]) minn = min(minn, deep[v[tmp]]); //从所以相连节点中选取编号最小的 if (!(--num[deep[now]])) break;//GAP优化,如果编号出现了断层,说明不能再增广,那么此时的答案即为最大流 num[deep[now] = minn + 1]++; cur[now] = point[now]; if (now != s) now = v[lastedge[now] ^ 1]; } } return ans; } int main() { tot = -1; memset(point, -1, sizeof(point)); memset(nxt, -1, sizeof(nxt)); m = getnum(); n = getnum(); for (int i = 1; i <= m; i++) { int x = getnum(); int y = getnum(); int cap = getnum(); addedge(x, y, cap); } cout << isap(1, n) << endl; }