zoukankan      html  css  js  c++  java
  • 网络流--最大流,最小割,费用流问题

    例题:

    1. P1231 教辅的组成

    2. P2598 [ZJOI2009]狼和羊的故事

    3. P4016 负载平衡问题

    • 最大流:

    贪心流程:

    1. 找一条(s)(t)的只经过(f(e)< c(e)) 的边的路径。

    2. 如果不存在满足条件的路径,则算法结束。否则,沿着该路径尽可能地增加(f(e)),返回第(1)步。这一步骤称作增广。

    反悔机制:

    1. 只利用满足(f(e)< c(e))(e)(f(e)> 0)(e)的反向边(rev(e)) ,寻找一条(s)(t)的路径。

    2. 如果不存在满足条件的路径,则算法结束。否则,沿着该路径尽可能地增加流,返回(1)。

    核心代码:

    bool BFS(int s){
    	queue<int> q;
    	q.push(s);memset(dis,0,sizeof(dis));dis[0] = 1;
    	while (!q.empty()){
    		int x = q.front();q.pop();
    		for (int i = head[x];i;i = ed[i].nxt){
    			int to = ed[i].to;
    			if (dis[to]||!ed[i].w) continue;
    			dis[to] = dis[x] + 1;
    			q.push(to);
    		}
    	}
    	return dis[n1+n2+n1+n3+1];
    }
    int DFS(int x,int limit){
    	if (!limit||x == n1+n2+n1+n3+1) return limit;
    	int res = 0;
    	for (int &i = head[x];i;i = ed[i].nxt){
    		int to = ed[i].to;
    		if (dis[to] != dis[x]+1) continue;
    		int tmp = DFS(to,min(limit,ed[i].w));
    		limit -= tmp,ed[i].w -= tmp,ed[i^1].w += tmp,res += tmp;
    		if (!limit) break;
    	}
    	return res;
    }
    int main(){
    	for (int i = 0;i <= n1+n2+n1+n3+1;i++) cur[i] = head[i];
    	while (BFS(0)){
    		while (tmp = DFS(0,inf)) ans += tmp;
    		for (int i = 0;i <= n1+n2+n1+n3+1;i++) head[i] = cur[i];
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    

    最小割:

    性质: 最小割 = 最大流

    费用流:

    在求解最大流时,我们在残量网络上不断贪心地增广而得到了最大流。现在边上多了费用,为了使最后总费用最小,我们自然能想到每次贪心地在残量网络上,沿着费用最小的那条路进行增广,此时残量网络中的反向边费用应该是原边费用的相反数,以保证退流操作是可逆、正确的。

    核心代码:

    bool SPFA(int s){
    	queue<int> q;
    	memset(vis,0,sizeof(vis));
    	for (int i = 1;i <= n;i++) dis[i] = inf;
    	q.push(s);dis[s] = 0;vis[s] = 1;
    	while (!q.empty()){
    		int x = q.front();q.pop();
    		vis[x] = 0;
    		for (int i = head[x];i;i = ed[i].nxt){
    			int to = ed[i].to;
    			if (!ed[i].lim) continue;
    			if (dis[to] > dis[x] + ed[i].w){
    				dis[to] = dis[x] + ed[i].w;
    				pre1[to] = x,pre2[to] = i;
    				if (!vis[to]){
    					vis[to] = 1;
    					q.push(to);
    				}
    			} 
    		}
    	}
    	return dis[t] != inf;
    }
    int main(){
    	n = read(),m = read(),s = read(),t = read();
    	for (int i = 1;i <= m;i++){
    		int u = read(),v = read(),w = read(),c = read();
    		add(u,v,w,c),add(v,u,0,-c);
    	}
    	int ans1 = 0,ans2 = 0;
    	while (SPFA(s)){
    		int tmp = inf;
    		for (int i = t;i != s;i = pre1[i]) tmp = min(tmp,ed[pre2[i]].lim);	
    		ans1 += tmp,ans2 += tmp*dis[t];
    		for(int i = t;i != s;i = pre1[i]) ed[pre2[i]].lim -= tmp,ed[pre2[i]^1].lim += tmp;
    	}
    	printf("%d %d
    ",ans1,ans2);
    	return 0;
    }
    

    例题:

    对于第一道题,想要尽可能的多组出书的配套方案,我们可以联想到最大流,那么问题就在于如何连边,因为给出的都是书和练习册,或者书和答案之间的关系,所以我们把书放在中间,练习册和答案分别和他连边。

    但是这就出现了一个问题,每本书只能选一次,怎样才能保证这个问题呢???我们把书拆成两个点,中间连一条边权为1的边就可以解决了。

    code:

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    #include <queue>
    using namespace std;
    int read(){
    	int x = 1,a = 0;char ch = getchar();
    	while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
    	while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
    	return x*a;
    }
    const int maxn = 1e5+10,inf = 1e9+7;
    int n1,n2,n3;
    int m1,m2;
    struct node{
    	int to,nxt,w;
    }ed[maxn*4];
    int head[maxn*4],tot = 1,cur[maxn*4];
    void add(int u,int to,int w){
    	ed[++tot].to = to;
    	ed[tot].w = w;
    	ed[tot].nxt = head[u];
    	head[u] = tot;
    }
    int dis[maxn*4];
    bool BFS(int s){
    	queue<int> q;
    	q.push(s);memset(dis,0,sizeof(dis));dis[0] = 1;
    	while (!q.empty()){
    		int x = q.front();q.pop();
    		for (int i = head[x];i;i = ed[i].nxt){
    			int to = ed[i].to;
    			if (dis[to]||!ed[i].w) continue;
    			dis[to] = dis[x] + 1;
    			q.push(to);
    		}
    	}
    	return dis[n1+n2+n1+n3+1];
    }
    int DFS(int x,int limit){
    	if (!limit||x == n1+n2+n1+n3+1) return limit;
    	int res = 0;
    	for (int &i = head[x];i;i = ed[i].nxt){
    		int to = ed[i].to;
    		if (dis[to] != dis[x]+1) continue;
    		int tmp = DFS(to,min(limit,ed[i].w));
    		limit -= tmp,ed[i].w -= tmp,ed[i^1].w += tmp,res += tmp;
    		if (!limit) break;
    	}
    	return res;
    }
    int main(){
    	n1 = read(),n2 = read(),n3 = read();
    	m1 = read();
    	for (int i = 1;i <= m1;i++){
    		int x = read(),y = read();
    		add(y,x+n2,1),add(x+n2,y,0);
    	}
    	m2 = read();
    	for (int i = 1;i <= m2;i++){
    		int x = read(),y = read();
    		add(x+n2+n1,y+n1+n1+n2,1),add(y+n1+n1+n2,x+n1+n2,0);
    	}
    	for (int i = 1;i <= n1;i++){
    		add(i+n2,i+n2+n1,1),add(i+n2+n1,i+n2,0);
    	}
    	for (int i = 1;i <= n2;i++) add(0,i,1),add(i,0,0);
    	for (int i = 1;i <= n3;i++) add(i+n1+n2+n1,n1+n2+n1+n3+1,1),add(n1+n2+n1+n3+1,i+n1+n2+n1,0);
    	int ans = 0,tmp;
    	for (int i = 0;i <= n1+n2+n1+n3+1;i++) cur[i] = head[i];
    	while (BFS(0)){
    		while (tmp = DFS(0,inf)) ans += tmp;
    		for (int i = 0;i <= n1+n2+n1+n3+1;i++) head[i] = cur[i];
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    

    第二道题主要看我们如何能把问题转化为最小割,连出一个四联通图后,建立源点和汇点,把羊向源点连边,狼向汇点连边,边权全为(inf),这样我们在求最小割的时候一定不会断掉他(因为他太大了啊)

    正确性: 狼和羊能相遇的充分条件是能通过狼和羊亮点从源点走到汇点,如果现在不连通,且断掉的不是连接源点和汇点这些边,那么一定是狼和羊之间的路径

    code:

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    #include <queue>
    using namespace std;
    int read(){
    	int  x = 1,a = 0;char ch = getchar();
    	while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
    	while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
    	return x*a;
    }
    const int maxn = 1000*1000+10,inf = 1e9+7;
    int n,m;
    int s,t;
    struct node{
    	int to,nxt,w;
    }ed[maxn*4];
    int head[maxn*4],tot = 1,cur[maxn*4];
    void add(int u,int to,int w){
    	ed[++tot].to = to;
    	ed[tot].nxt = head[u];
    	ed[tot].w = w;
    	head[u] = tot;
    }
    int dis[maxn*4];
    bool BFS(int s){
    	queue<int> q;
    	q.push(s);memset(dis,0,sizeof(dis));dis[0] = 1;
    	while (!q.empty()){
    		int x = q.front();q.pop();
    		for (int i = head[x];i;i = ed[i].nxt){
    			int to = ed[i].to;
    			if (dis[to]||!ed[i].w) continue;
    			dis[to] = dis[x] + 1;
    			q.push(to);
    		}
    	}
    	return dis[t];
    }
    int DFS(int x,int limit){
    	if (!limit||x == t) return limit;
    	int res = 0;
    	for (int &i = head[x];i;i = ed[i].nxt){
    		int to = ed[i].to;
    		if (dis[to] != dis[x]+1) continue;
    		int tmp = DFS(to,min(limit,ed[i].w));
    		limit -= tmp,ed[i].w -= tmp,ed[i^1].w += tmp,res += tmp;
    		if (!limit) break;
    	}
    	return res;
    }
    int main(){
    	n =  read(),m = read();
    	s = 0,t = (1+n)*m+1;
    	for (int i = 1;i <= n;i++){
    		for (int j = 1;j <= m;j++){
    			int x = read();
    			int pos1 = i*m+j,pos2 = (i-1)*m+j,pos3 = (i+1)*m+j,pos4 = i*m+j-1,pos5 = i*m+j+1;
    			if (i > 1) add(pos1,pos2,1),add(pos2,pos1,0);
    			if (i < n) add(pos1,pos3,1),add(pos3,pos1,0);
    			if (j > 1) add(pos1,pos4,1),add(pos4,pos1,0);
    			if (j < m) add(pos1,pos5,1),add(pos5,pos1,0);
    			if (x == 1) add(pos1,t,inf),add(t,pos1,0);
    			if (x == 2) add(s,pos1,inf),add(pos1,s,0);
    		}	
    	}
    	int tmp,ans = 0;
    	for (int i = s;i <= t;i++) cur[i] = head[i];
    	while (BFS(0)){
    		while (tmp = DFS(0,inf)) ans += tmp;
    		for (int i = s;i <= t;i++) head[i] = cur[i];
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    

    第三题而言重点还是在建模,贪心的想,多的我们移走,少的我们补充,正好的我们不动,那么建边就分为以下几种情况:

    1. 比平均值大的仓库,从源点向他连一条流量为(a_i-sum)的边,费用为0,说明要从他移出

    2. 比平均值小的仓库,从他向汇点连一条流量为(sum-a_i)的边,费用为0,说明要补充他

    3. 相邻仓库间建一条流量为(inf),费用为1的边,可以无限移动,但是与源点汇点连的边限制了他们

    code:

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    using namespace std;
    int read(){
    	int x = 1,a = 0;char ch = getchar();
    	while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch =  getchar();}
    	while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
    	return x*a;
    }
    const int maxn = 500,inf = 1e9+7;
    int n,a[maxn],s,t;
    struct node{
    	int to,nxt,w,lim;
    }ed[maxn*4];
    int head[maxn*4],tot = 1;
    void add(int u,int to,int lim,int w){
    	ed[++tot].to = to;
    	ed[tot].w = w;
    	ed[tot].lim = lim;
    	ed[tot].nxt = head[u];
    	head[u] = tot;
    }
    int vis[maxn],dis[maxn],pre1[maxn],pre2[maxn];
    bool SPFA(int s){
    	queue<int> q;q.push(s);
    	memset(vis,0,sizeof(vis));vis[s] = 1;
    	for (int i = 0;i <= n+1;i++) dis[i] = inf;dis[s] = 0;
    	while (!q.empty()){	
    		int x = q.front();q.pop();
    		vis[x] = 0;
    		for (int i = head[x];i;i = ed[i].nxt){
    			int to = ed[i].to;
    			if (!ed[i].lim) continue;
    			if (dis[to] > dis[x]+ed[i].w){
    				dis[to] = dis[x] + ed[i].w;
    				pre1[to] = x,pre2[to] = i;
    				if (!vis[to]){
    					vis[to] =  1;
    					q.push(to);
    				}
    			}
    		}
    	}
    	return dis[t] != inf;
    }
    int main(){
    	n = read();
    	s = 0,t = n+1;
    	int sum = 0;
    	for (int i = 1;i <= n;i++) a[i] = read(),sum += a[i];
    	sum /= n;
    	for (int i = 1;i <= n;i++){
    		if (a[i] < sum) add(i,t,sum-a[i],0),add(t,i,0,0);
    		if (a[i] > sum) add(s,i,a[i]-sum,0),add(i,s,0,0);
    		if (i > 1) add(i,i-1,inf,1),add(i-1,i,0,-1);
    		if (i < n) add(i,i+1,inf,1),add(i+1,i,0,-1);
    	}
    	add(1,n,inf,1),add(n,1,0,-1);
    	add(n,1,inf,1),add(1,n,0,-1);
    	int ans = 0;
    	while (SPFA(s)){
    		int tmp = inf;
    		for (int i = t;i != s;i = pre1[i]) tmp = min(tmp,ed[pre2[i]].lim);
    		ans += tmp*dis[t];
    		for (int i = t;i != s;i = pre1[i]) ed[pre2[i]].lim -= tmp,ed[pre2[i]^1].lim += tmp;
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    AB压力测试(Windows)
    Ensure You Are Not Adding To Global Scope in JavaScript(转)
    使用jasmine来对js进行单元测试
    HTML5安全:CORS(跨域资源共享)简介(转)
    asp.net+jquery Jsonp使用方法(转)
    在ios上时间无法parse返回 "Invalid Date"(转)
    用document.domain完美解决Ajax跨子域 (转)
    IE10、IE11 User-Agent 导致的 ASP.Net 网站无法写入Cookie 问题
    NodeJs:module.filename、__filename、__dirname、process.cwd()和require.main.filename 解惑(转)
    关于反射的一些总结(转)
  • 原文地址:https://www.cnblogs.com/little-uu/p/13984543.html
Copyright © 2011-2022 走看看