题目就不贴出来了,网上很多,但是网上的题解基本都是在默认读者已经懂网络流/费用流的前提下写的,解析很少,我就以初学者的角度记录一下这个题的学习过程。
- 首先这个题是无汇源有上下界的费用流最大流问题(好像是循环流?我还没太懂)
- 无汇源是因为这个题并没有特殊的点,用术语就是所有的点都满足流量守恒,没有那种凭空产生流量的点。作为对比,下面有一个题目(摘自 https://www.cnblogs.com/kane0526/archive/2013/04/05/3001108.html)
** 题目大意:一个屌丝给m个女神拍照,计划拍照n天,每一天屌丝最多个C个女神拍照,每天拍照数不能超过D张,而且给每个女神i拍照有数量限制[Li,Ri],对于每个女神n天的拍照总和不能超过Gi,如果有解求屌丝最多能拍多少张照,并求每天给对应女神拍多少张照;否则输出-1。**
在这个题中,这个屌丝就是一个可以凭空产生流量的点,他拍照什么的是随自己心情的。 - 有上下界是因为下界:有的管道必须经过(清洁),有的管道不必须清洁; 上界:有的管道只能经过一次,有的管道却可以经过无数次。
- 费用流是因为经过管道要付出代价,无代价即成了网络流。
- 无汇源是因为这个题并没有特殊的点,用术语就是所有的点都满足流量守恒,没有那种凭空产生流量的点。作为对比,下面有一个题目(摘自 https://www.cnblogs.com/kane0526/archive/2013/04/05/3001108.html)
- 然后是建图过程
- 注意每个管道是一个边,而不是一个点。只能经过一个点一次的话,要用拆点;只能经过边一次的话,用容量限制就可以了。
- 首先列出每类边的上下界: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我们就不用管了,然后上界就和图中边的容量对应就可以。如图所示,(用画流程图的工具画的图,所以比较丑,将就着作为参考,还是手画一下比较好)
- 然后添加超级源点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;
}