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;
    }
    
  • 相关阅读:
    第三方驱动备份与还原
    Greenplum 解决 gpstop -u 指令报错
    yum安装(卸载)本地rpm包的方法(卸载本地安装的greenplum 5.19.rpm)
    Java JUC(java.util.concurrent工具包)
    netty 详解(八)基于 Netty 模拟实现 RPC
    netty 详解(七)netty 自定义协议解决 TCP 粘包和拆包
    netty 详解(六)netty 自定义编码解码器
    netty 详解(五)netty 使用 protobuf 序列化
    netty 详解(四)netty 开发 WebSocket 长连接程序
    netty 详解(三)netty 心跳检测机制案例
  • 原文地址:https://www.cnblogs.com/little-uu/p/13984543.html
Copyright © 2011-2022 走看看