CCF201812-5 管道清洁 建图过程详解+代码

题目就不贴出来了,网上很多,但是网上的题解基本都是在默认读者已经懂网络流/费用流的前提下写的,解析很少,我就以初学者的角度记录一下这个题的学习过程。

  • 首先这个题是无汇源有上下界的费用流最大流问题(好像是循环流?我还没太懂)
    • 无汇源是因为这个题并没有特殊的点,用术语就是所有的点都满足流量守恒,没有那种凭空产生流量的点。作为对比,下面有一个题目(摘自 https://www.cnblogs.com/kane0526/archive/2013/04/05/3001108.html)
      ** 题目大意:一个屌丝给m个女神拍照,计划拍照n天,每一天屌丝最多个C个女神拍照,每天拍照数不能超过D张,而且给每个女神i拍照有数量限制[Li,Ri],对于每个女神n天的拍照总和不能超过Gi,如果有解求屌丝最多能拍多少张照,并求每天给对应女神拍多少张照;否则输出-1。**
      在这个题中,这个屌丝就是一个可以凭空产生流量的点,他拍照什么的是随自己心情的。
    • 有上下界是因为下界:有的管道必须经过(清洁),有的管道不必须清洁; 上界:有的管道只能经过一次,有的管道却可以经过无数次。
    • 费用流是因为经过管道要付出代价,无代价即成了网络流。
  • 然后是建图过程
    • 注意每个管道是一个边,而不是一个点。只能经过一个点一次的话,要用拆点;只能经过边一次的话,用容量限制就可以了。
    • 首先列出每类边的上下界A类[1,inf],B类[1,1],C类[0,inf],D类[0,1]。A[1,inf]代表A边下界为1,上界无穷,即必须经过至少一次A边,至多无限次,其它的类似。
    • 转换上下界。如果下界不为0(A,B两类),就将上下界[a,b]转为[0,b-a],最后就成了这样子A类[0,inf-1],B类[0,0],C类[0,inf],D类[0,1],然后inf和inf-1的意思是一样的,所以也可以是这样A类[0,inf],B类[0,0],C类[0,inf],D类[0,1]。这是为了建模方便,转换后,下界默认为0我们就不用管了,然后上界就和图中边的容量对应就可以。如图所示,(用画流程图的工具画的图,所以比较丑,将就着作为参考,还是手画一下比较好)
C:f=INF w=1
B:f=0 w=1
-C:f=0 w=-1
-B:f=0,w=-1
B:f=0 w=1
A:f=inf w=1
-D:f=0 w=-1
-B:f=0 w=-1
-D:f=0 w=-1
C:f=inf w=1
-A:f=0 w=-1
D:f=1 w=1
-C:f=0 w=-1
D:f=1 w=1
1
2
3
4
5
  • 然后添加超级源点st和一个超级终点sd,使得满足容量守恒
    令d(u)=该点入下界流和-该点出下界流和,其实这个值就等于u入流的和-u出流的和,不过用下界来进行计算更高效。
    如果d(u)>0 则连边s–>u flow=d(u)
    如果d(u)<0 则连边u–>t flow=-d(u)
    图略了,画得不好看。。。
  • 最后就是跑最大费用流的模板就可以了,模板的话很多书上都有。

代码如下:
参考https://blog.csdn.net/qq_43202683/article/details/90047864
以及李煜东的《算法竞赛进阶指南》费用流一节

#include <stdio.h>
#include <queue>
#include <string.h>

using namespace std;

const int N = 5010, M = 200010;
const int inf = 0x3f3f3f3f;
int ver[M], edge[M], cost[M], Next[M], head[M];
int ver_pre[M];//调试用
int d[N], incf[N], pre[N], v[N];//spfa算法
int n, k, tot, s, t, maxflow, ans;
int m, du[N];//下界补边要用
int cnt;//记录下界和
int num;//通过下界的费用,由于上下界的转换,算法里面并没有计算到这里的费用

void add(int x, int y, int z, int c){
	//x->y
	ver[++tot]=y, edge[tot]=z, cost[tot]=c;
	ver_pre[tot] = x;
	Next[tot]=head[x], head[x]=tot;
	//y->x
	ver[++tot]=x, edge[tot]=0, cost[tot]=-c;
	ver_pre[tot] = y;
	Next[tot]=head[y], head[y]=tot;
}

bool spfa()
{
	queue<int> q;
	memset(d, 0x3f, sizeof(d));//dist数组  inf
	memset(v, 0, sizeof(v));//是否在队列中
	q.push(s);
	d[s] = 0;
	v[s] = 1;
 	incf[s] = 1<<30;
	while(!q.empty()){
		int x = q.front(); q.pop(); v[x] = 0;
		for (int i = head[x]; i; i = Next[i]){
			if (edge[i]){
				int y = ver[i];
				if (d[y] > d[x]+cost[i]){
					d[y] = d[x]+cost[i];
					incf[y] = min(incf[x], edge[i]);
					pre[y] = i;
					if (!v[y]){
						v[y] = 1;
						q.push(y);
					}
				}
			}
		}

	}
	if (d[t] == 0x3f3f3f3f) return false;
	else return true;
}

void update()
{
	int x = t;
	while (x != s){
		int i =pre[x];
		edge[i] -= incf[t];
		edge[i^1] += incf[t];
		x = ver[i^1];
	}
	maxflow += incf[t]; 
	ans += d[t]*incf[t];
}


int main(int argc, char* argv[])
{
	char* file = (char*)"ccf3.txt";
	if(argc==2) file = argv[1];
	//freopen(file, "r", stdin);

	int T, S, E;
	scanf("%d%d%d", &T, &S, &E);
	while(T--){
		scanf("%d%d", &n, &m);
		s = 0, t = n+1;
		tot = 1;
		cnt = 0; 
		maxflow = 0;
		num = 0;
		ans = 0;
		memset(head, 0, sizeof(head));
		memset(du, 0, sizeof(du));
		for(int i=1; i<=m; i++){
			int u,v; char t;
			scanf("%d %d %c", &u, &v, &t);
			getchar();
			switch(t){
				case 'A':
					add(u,v,inf,E);	
					du[u]--,du[v]++;//原本上下界为(1,inf),调整为(1,inf-1或inf)后会出现流量不守恒,所以要补边,同时由于下界为1,所以du只加了1
					num+=E;
					break;
				case 'B':
					//原本流量上下界是(1,1),但是调整后变成了(0,0)
					//add(u,v,0,E);//由于上界为0,所以其实可以删除此边
					du[u]--,du[v]++;
					num+=E;
					break;
				case 'C'://下界本身就为0
					add(u,v,inf,E);
					break;
				case 'D':
					add(u,v,1,E);
					break;
			}
		}
		for(int i=1; i<=n; i++){
			if(du[i] > 0){
				cnt += du[i];//计算s的出流总和
				add(s,i,du[i],0);
			}else if(du[i] < 0){
				add(i,t,-du[i],0);
			}
		}

		while (spfa())
			update();
		if(maxflow==cnt) printf("%d\n", ans+num);
		else printf("%d\n", -1);
	}


	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值