zoukankan      html  css  js  c++  java
  • 网络流模板

    在这里我只放我的模板和一些我个人的“理解”。。

    最大流测试题: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连到了汇上。。而汇到源连一条无限容量的弧是为了让那个下界流从这里流回去。。-(在无源无汇的话其实可以不必连这条边)

  • 相关阅读:
    [Unity3D] 如何将飞飞游戏资源提取并加载到uinty3d中
    [Unity3D] 解决提示参数动作文件不存在的错误Parameter 'XXX' does not exist.
    [Unity3D] 字体垂直自动滚动&鼠标拖拽滑动字体滚动
    [Unity3D] 图集按照顺序显示(幻灯片效果)&指定间隔时间显示
    [Unity3D] 在UI界面上显示播放视频
    TypeScript
    Css 设置超过再两行显示省略号
    vue scss 样式穿透
    JavaScript 严格模式(strict mode)
    Webpack file-loader 和 url-loader
  • 原文地址:https://www.cnblogs.com/iwtwiioi/p/3819242.html
Copyright © 2011-2022 走看看