在这里我只放我的模板和一些我个人的“理解”。。
最大流测试题:usaco草地排水
EK:
时间复杂度:O(VE^2)
代码复杂度:最易
代码:
#include <cstdio> #include <cstring> #include <queue> using namespace std; #define CLR(a) memset(a, 0, sizeof(a)) #define FOR(i,a,n) for(i=a;i<=n;++i) const int maxn = 205, oo = ~0u >> 1; int cap[maxn][maxn], ihead[maxn], inext[maxn<<2], iv[maxn<<2], cnt; int p[maxn]; queue<int> q; int n, m; int min(const int& a, const int& b) { return a < b ? a : b; } int EK(int s, int t) { int u, v, i, flow = 0, aug; while(1) { CLR(p); q.push(s); while(!q.empty()) { u = q.front(); q.pop(); for(i = ihead[u]; i ; i = inext[i]) if(cap[u][iv[i]] > 0 && !p[iv[i]]) { p[iv[i]] = u; q.push(iv[i]); } } if(!p[t]) break; aug = oo; for(v = t; v != s; v = p[v]) aug = min(aug, cap[p[v]][v]); for(v = t; v != s; v = p[v]) cap[p[v]][v] -= aug, cap[v][p[v]] += aug; flow += aug; } return flow; } void add(int u, int v, int c) { inext[++cnt] = ihead[u]; ihead[u] = cnt; iv[cnt] = v; cap[u][v] += c; inext[++cnt] = ihead[v]; ihead[v] = cnt; iv[cnt] = u; } int main() { scanf("%d%d", &m, &n); int u, v, c; for(int i = 1; i <= m; ++i) { scanf("%d%d%d", &u, &v, &c); add(u, v, c); } printf("%d", EK(1, n)); return 0; }
Dinic:
时间复杂度:O(V^2E)
代码复杂度:较长但不易出错
PS:这是加了当前弧优化的
代码1(这份代码的cap是二维的):
#include <cstdio> #include <cstring> #include <queue> using namespace std; #define CLR(a) memset(a, 0, sizeof(a)) #define FOR(i,a,n) for(i=a;i<=n;++i) const int maxn=205, oo=~0u>>1, maxm=maxn<<2; int cap[maxn][maxn], ihead[maxn], inext[maxm], iv[maxm], cnt; int vis[maxn], d[maxn], cur[maxn]; queue<int> q; int n, m, s, t; int min(const int& a, const int& b) { return a < b ? a : b; } bool BFS() { //建立层次图 CLR(vis); q.push(s); d[s]=0; vis[s]=1; //这里记住要加上vis[s]=1; int u, i; while(!q.empty()) { u=q.front(); q.pop(); for(i=ihead[u]; i; i=inext[i]) if(!vis[iv[i]] && cap[u][iv[i]]) { d[iv[i]]=d[u]+1; vis[iv[i]]=1; q.push(iv[i]); } } return vis[t]; } int DFS(int u, int a) { if(u==t || !a) return a; int f, flow=0; for(int& i=cur[u]; i; i=inext[i]) if(d[u]+1==d[iv[i]] && (f=DFS(iv[i], min(a, cap[u][iv[i]])))>0 ) { //沿着层次图走,并且这是条增广路 flow+=f, a-=f, cap[u][iv[i]]-=f, cap[iv[i]][u]+=f;//a保存的是之前路径的最小,所以不一定是等于f,所以要减掉f if(!a) break; //说明不可能再有增广路,也就是说,前面断了。 } return flow; } int dinic() { int flow=0, i; while(BFS()) { FOR(i, 1, n) cur[i]=ihead[i]; flow+=DFS(s, oo); } return flow; } void add(int u, int v, int c) { inext[++cnt]=ihead[u]; ihead[u]=cnt; iv[cnt]=v; cap[u][v]+=c; inext[++cnt]=ihead[v]; ihead[v]=cnt; iv[cnt]=u; } int main() { scanf("%d%d", &m, &n); int u, v, c; for(int i=1; i<=m; ++i) { scanf("%d%d%d", &u, &v, &c); add(u, v, c); } s=1; t=n; printf("%d", dinic()); return 0; }
代码2(cap已改为1维,并做了相应更改):
#include <cstdio> #include <cstring> #include <queue> using namespace std; #define CC(a, c) memset(a, c, sizeof(a)) #define FOR(i,a,n) for(i=a;i<=n;++i) const int maxn=300, oo=~0u>>1, maxm=50000; int S, T, cap[maxm], d[maxn], cur[maxn], vis[maxn]; queue<int> q; int N, ihead[maxn], inext[maxm], to[maxm], from[maxm], cnt; //听取wyl意见,从2开始编号 int min(const int& a, const int& b) { return a < b ? a : b; } bool BFS() { //建立层次图 CC(vis, 0); q.push(S); d[S]=0; vis[S]=1; //这里记住要加上vis[s]=1; int u, i; while(!q.empty()) { u=q.front(); q.pop(); for(i=ihead[u]; i; i=inext[i]) if(!vis[to[i]] && cap[i]) { d[to[i]]=d[u]+1; vis[to[i]]=1; q.push(to[i]); } } return vis[T]; } int DFS(int u, int a) { if(u==T || !a) return a; int f, flow=0; for(int& i=cur[u]; i; i=inext[i]) if(d[u]+1==d[to[i]] && (f=DFS(to[i], min(a, cap[i])))>0 ) { //沿着层次图走,并且这是条增广路 flow+=f, a-=f, cap[i]-=f, cap[i^1]+=f;//a保存的是之前路径的最小,所以不一定是等于f,所以要减掉f if(!a) break; //说明不可能再有增广路,也就是说,前面断了。 } return flow; } int dinic() { int flow=0, i; while(BFS()) { FOR(i, 1, N) cur[i]=ihead[i]; flow+=DFS(S, oo); } return flow; } void add(int u, int v, int c) { inext[++cnt]=ihead[u]; ihead[u]=cnt; to[cnt]=v; cap[cnt]=c; inext[++cnt]=ihead[v]; ihead[v]=cnt; to[cnt]=u; } void init(int s, int t, int n) { S=s; T=t; N=n; cnt=1; CC(ihead, 0); } int main() { int n, m, u, v, c, i; scanf("%d%d", &m, &n); init(1, n, n); for(i=1; i<=m; ++i) { scanf("%d%d%d", &u, &v, &c); add(u, v, c); } printf("%d", dinic()); return 0; }
isap(国人都叫sap,其实百度一下有解释的):
时间复杂度:O(V^2E)实际快多了,这是我将来用的算法
代码复杂度:短但细节太多,易错
PS:这是加了当前弧优化、gap优化的、暂无瓶颈优化 | 话说,调sap我都调怕了,有点恐惧症了。。
代码1(容量我还是用二维的存):
#include <cstdio> #include <cstring> #include <queue> using namespace std; #define CC(a, c) memset(a, c, sizeof(a)) #define FOR(i,a,n) for(i=a;i<=n;++i) const int maxn=205, oo=~0u>>1, maxm=maxn<<2; int cap[maxn][maxn], ihead[maxn], inext[maxm], iv[maxm], cnt; int p[maxn], d[maxn], cur[maxn], gap[maxn]; int n, m, s, t; int min(const int& a, const int& b) { return a < b ? a : b; } int isap() { int flow=0, u, v, i, f=oo; CC(d, 0); CC(gap, 0); for(i=1; i<=n; ++i) cur[i]=ihead[i]; u=p[s]=s; gap[0]=n; //这里预处理很重要,第一个赋值给p[s]是因为后面会调用,第二个是因为没有单独的bfs来处理d数组才要这样设置,否则一两次循环就要跳出了 while(d[s]<n) { for(i=cur[u]; i; i=inext[i]) if(cap[u][iv[i]] && d[u]==d[iv[i]]+1) break; if(i) { cur[u]=i; v=iv[i]; f=min(f, cap[u][v]); p[v]=u; u=v; //这里记得将u重新赋值过,cur当前弧优化 if(v==t) { for(; v!=s; v=p[v]) cap[p[v]][v]-=f, cap[v][p[v]]+=f; u=s; flow+=f; f=oo; //将u退回远点,似乎有叫瓶颈优化的东西,到时候再说吧。记得将最小流f重置 } } else { if( !(--gap[d[u]]) ) break; //gap优化,即发现断层立即终止 d[u]=n; //当没有发现允许弧时,要重新设置 cur[u]=ihead[u]; //这里最坑,,,还是请大神帮忙调试出来的0 0,应该这里是瓶颈优化的地方 for(i=cur[u]; i; i=inext[i]) if(cap[u][iv[i]] && d[u]>d[iv[i]]+1) d[u]=d[iv[i]]+1; gap[d[u]]++; u=p[u]; } } return flow; } void add(int u, int v, int c) { inext[++cnt]=ihead[u]; ihead[u]=cnt; iv[cnt]=v; cap[u][v]+=c; inext[++cnt]=ihead[v]; ihead[v]=cnt; iv[cnt]=u; } int main() { scanf("%d%d", &m, &n); int u, v, c, i; for(i=1; i<=m; ++i) { scanf("%d%d%d", &u, &v, &c); add(u, v, c); } s=1; t=n; printf("%d", isap()); return 0; }
代码2(已改为1维并相应修改):
#include <cstdio> #include <cstring> using namespace std; #define CC(a, c) memset(a, c, sizeof(a)) #define FOR(i,a,n) for(i=a;i<=n;++i) //听取wyl意见,从2开始编号 const int maxn=1000, maxm=1000000, oo=100000000; int cap[maxm], d[maxn], cur[maxn], gap[maxn], p[maxn]; int ihead[maxn], inext[maxm], to[maxm], from[maxm], cnt; int min(const int& a, const int& b) { return a < b ? a : b; } int isap(int s, int t, int n) { int flow=0, i, u, f=oo; CC(gap, 0); CC(d, 0); FOR(i, 0, n) cur[i]=ihead[i]; u=s; gap[0]=n; //这里预处理很重要,第二个是因为没有单独的bfs来处理d数组才要这样设置,否则一两次循环就要跳出了 while(d[s]<n) { for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]==d[to[i]]+1) break; if(i) { cur[u]=i; f=min(f, cap[i]); p[to[i]]=i; u=to[i]; //这里记得将u重新赋值过,cur当前弧优化 //这里注意,p要赋值成i if(u==t) { for(; u!=s; u=from[p[u]]) cap[p[u]]-=f, cap[p[u]^1]+=f; flow+=f; f=oo; } } else { if( !(--gap[d[u]]) ) break; //gap优化,即发现断层立即终止 d[u]=n; //当没有发现允许弧时,要重新设置 cur[u]=ihead[u]; //这里最坑,,,还是请大神帮忙调试出来的0 0,应该这里是瓶颈优化的地方 for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]>d[to[i]]+1) d[u]=d[to[i]]+1; gap[d[u]]++; if(u!=s) u=from[p[u]];//这里要注意 } } return flow; } void add(int u, int v, int c) { inext[++cnt]=ihead[u]; ihead[u]=cnt; from[cnt]=u; to[cnt]=v; cap[cnt]=c; inext[++cnt]=ihead[v]; ihead[v]=cnt; from[cnt]=v; to[cnt]=u; cap[cnt]=0; } void init() { cnt=1; CC(ihead, 0); } int main() { int n, m, u, v, c, i; scanf("%d%d", &m, &n); init(); for(i=1; i<=m; ++i) { scanf("%d%d%d", &u, &v, &c); add(u, v, c); } printf("%d", isap(1, n, n)); return 0; }
代码3(根据大神的指点,优化了求增广流f):
#include <cstdio> #include <cstring> using namespace std; #define CC(a, c) memset(a, c, sizeof(a)) #define FOR(i,a,n) for(i=a;i<=n;++i) //听取wyl意见,从2开始编号 const int maxn=1000, maxm=1000000, oo=100000000; int cap[maxm], d[maxn], cur[maxn], gap[maxn], p[maxn]; int ihead[maxn], inext[maxm], to[maxm], from[maxm], cnt; int min(const int& a, const int& b) { return a < b ? a : b; } int isap(int s, int t, int n) { int flow=0, i, u, f=oo; CC(gap, 0); CC(d, 0); FOR(i, 0, n) cur[i]=ihead[i]; u=s; gap[0]=n; //这里预处理很重要,第二个是因为没有单独的bfs来处理d数组才要这样设置,否则一两次循环就要跳出了 while(d[s]<n) { for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]==d[to[i]]+1) break; if(i) { cur[u]=i; p[to[i]]=i; u=to[i]; //这里记得将u重新赋值过,cur当前弧优化 这里注意,p要赋值成i if(u==t) { for(f=oo; u!=s; u=from[p[u]]) f=min(f, cap[p[u]]); //还是这样优化吧,开个数组和这样差不多;如果在前面min的话,会找同一增广路几次 for(u=t; u!=s; u=from[p[u]]) cap[p[u]]-=f, cap[p[u]^1]+=f; flow+=f; } } else { if( !(--gap[d[u]]) ) break; //gap优化,即发现断层立即终止 d[u]=n; //当没有发现允许弧时,要重新设置 cur[u]=ihead[u]; //这里最坑,,,还是请大神帮忙调试出来的0 0,应该这里是瓶颈优化的地方 for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]>d[to[i]]+1) d[u]=d[to[i]]+1; gap[d[u]]++; if(u!=s) u=from[p[u]];//这里要注意 } } return flow; } void add(int u, int v, int c) { inext[++cnt]=ihead[u]; ihead[u]=cnt; from[cnt]=u; to[cnt]=v; cap[cnt]=c; inext[++cnt]=ihead[v]; ihead[v]=cnt; from[cnt]=v; to[cnt]=u; cap[cnt]=0; } void init() { cnt=1; CC(ihead, 0); } int main() { int n, m, u, v, c, i; scanf("%d%d", &m, &n); init(); for(i=1; i<=m; ++i) { scanf("%d%d%d", &u, &v, &c); add(u, v, c); } printf("%d", isap(1, n, n)); return 0; }
我自己的瞎扯:
-
网络流为什么要有反向弧:
最直观的是= =,如果自己原来的弧的点被其它的s-t占掉了,还可以通过反向弧从占掉那个点的弧(u,v)中的u流回去,去占它的点,从而构成一条新的s-t -
isap的Gap优化的解释:
断层这个概念,可以这样想:
b
/
a -- c
d
这里我们假设d(a)=5
此时假设b后的增广路已经完了,那么d(b) = n,但是我们发现,还有d(c)=4,即没有断层。
一直下去,直到d(c)和d(d)都为n了,此时没有一个d(i)=4,所以,发生断层,此时d(i)=4的数目为0,所以就可以结束程序了。
这就是所谓的Gap优化~ -
网络流这一类题的初始化:
首先就要将所有点和该连的弧给连了,否则容易出错,什么拆点啊这些操作首先搞掉。
-
网络流上下界的胡扯解释:
为什么一条边(u,v)的的下界是从附加源连到v呢?
我是这样认为的: 从源开始流,又流回了u,再流回汇,如果满了,就能判断了0.0
同理,将u连到了汇上。。而汇到源连一条无限容量的弧是为了让那个下界流从这里流回去。。-(在无源无汇的话其实可以不必连这条边)