1. 无源汇可行流
对于有下界的网络流,首先要想办法去掉下界,做法是把原图中的流量分成流b和f’,其中b的流量为原流量下界, f′=f−b 。
虚拟一个源点s和一个汇点t,对每个点u,设 D(u)=∑bin−∑bout ,若 D(u)>0 ,从s连一条容量为D(u)的流到u,否则连一条容量为-D(u)的流到t。其实这个做法相当于对于每一个流入u的下界,连一条s到u的流,对于每一条流出u的下界,连一条u到t的流,这样的话原图中的流量下界都由s和t来提供,原图中的流流量变为 f−b ,容易发现s流出的流和t流入的流流量总和一定相等,如果原图满足下界限制的话,s和t的流都应该是满流的,这时候在原图中产生的流量可以认为是为了满足下界而做出的流量调整。
建完图,跑一遍最大流,如果s->t满流则说明有可行流,一条可行流的边上流量为新图中流的流量+它的下界。
例题:zoj2314
#include
using namespace std;
const int inf = 0x3f3f3f3f;
int n, m, tot,s,t;
int head[205], cur[205], h[205], q[205], in[205];
int l[100005];
struct edge { int to, next, cap; }e[100005];
void init() {
tot = 0;
memset(head, -1, sizeof(head));
}
void addedge(int u, int v, int w) {
e[tot].to = v; e[tot].next = head[u]; e[tot].cap = w; head[u] = tot++;
e[tot].to = u; e[tot].next = head[v]; e[tot].cap = 0; head[v] = tot++;
}
bool bfs() {
memset(h,-1,sizeof(h));
int tmp = 0, w = 1; q[s] = 0; h[s] = 0;
while (tmp != w) {
int now = q[tmp]; tmp++;
for (int i = head[now]; i!=-1; i = e[i].next)
if (e[i].cap&&h[e[i].to] == -1) {
h[e[i].to] = h[now] + 1;
q[w++] = e[i].to;
}
}
if (h[t] == -1)return 0;
return 1;
}
int dfs(int x, int f) {
if (x == t)return f;
int w, used = 0;
for (int i = cur[x]; i!=-1; i = e[i].next)
if (h[e[i].to] == h[x] + 1) {
w = f - used; w = dfs(e[i].to, min(e[i].cap, w));
e[i].cap -= w; if (e[i].cap)cur[x] = i; e[i ^ 1].cap += w;
used += w; if (used == f)return f;
}
if (!used)h[x] = 1;
return used;
}
int dinic() {
int ret = 0;
while (bfs()) {
for (int i = 0; i <= t; i++)
cur[i] = head[i];
ret += dfs(s, inf);
}
return ret;
}
int main() {
int T, u, v, c;
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
s = 0,t = n + 1;
init();
memset(in,0,sizeof(in));
for (int i = 0; i<m; i++){
scanf("%d%d%d%d", &u, &v, l + i, &c);
addedge(u, v, c - l[i]);
in[u] -= l[i];
in[v] += l[i];
}
int sum = 0;
for(int i = 1;i<=n;i++){
if(in[i] > 0){
addedge(s,i,in[i]);
sum += in[i];
}
else addedge(i,t,-in[i]);
}
if(sum != dinic()){
printf("NO\n\n");
continue;
}
printf("YES\n");
for(int i = 0;i<m;i++){
printf("%d\n",e[i<<1|1].cap + l[i]);
}
printf("\n");
}
}
2. 有源汇可行流
有源汇可行流的情况是在上一种情况的基础上指定了源点和汇点。解决这种问题的方法是转换为无源的情况(和下面两个问题的解决方法一样)。在原图中连一条t到s,容量为INF的流。虚拟出一个超级源点ss和超级汇点tt,按上个问题的方式建图。这里连一条t到s的流的意义在于完成原图中下界约束的传递关系,这是我的理解…这样的话ss和tt间流的传递就成为了一个整体…实际上t->s的流的流量即可行流s->t的流量。(好像说了句废话,不过这个性质在后面会被用到)
例题:zoj2396
int main(){
// freopen("a.in","r",stdin);
int T;
scanf("%d",&T);
int c,x,y,z;
char str[3];
while(T--){
scanf("%d%d",&m,&n);
init();
s = n + m + 1;
t = s + 1;
for(int i = 1;i<=m;i++){
scanf("%d",&c);
// c/c
addedge(s,i,0);
in[s] -= c;
in[i] += c;
}
for(int i = 1;i<=n;i++){
scanf("%d",&c);
addedge(m+i,t,0);
in[m+i] -= c;
in[t] += c;
}
scanf("%d",&c);
while(c--){
scanf("%d%d%s%d",&x,&y,str,&z);
int i1,i2,j1,j2;
if(x == 0){
i1 = 1;i2 = m;
}else
i1 = i2 =x;
if(y == 0){
j1 = 1;j2 = n;
}else
j1 = j2 = y;
for(int i = i1;i<=i2;i++){
for(int j = j1;j<=j2;j++){
if(str[0] == '>'){
lim[0][i][j] = max(lim[0][i][j],z+1);
}else if(str[0] == '<'){
lim[1][i][j] = min(lim[1][i][j],z-1);
}else{
lim[0][i][j] = max(lim[0][i][j],z);
lim[1][i][j] = min(lim[1][i][j],z);
}
}
}
}
bool flag = 0;
for(int i = 1;i<=m;i++){
for(int j = 1;j<=n;j++){
if(lim[0][i][j] > lim[1][i][j]){
flag = 1;
break;
}
addedge(i,j+m,lim[1][i][j] - lim[0][i][j]);
id[i][j] = tot-1;
in[i] -= lim[0][i][j];
in[m+j] += lim[0][i][j];
}
if(flag) break;
}
if(flag){
printf("IMPOSSIBLE\n");
continue;
}
addedge(t,s,inf);
int num = t;
s += 2;t += 2;
int sum = 0;
for(int i = 1;i<=num;i++){
if(in[i] > 0){
addedge(s,i,in[i]);
sum += in[i];
}else
addedge(i,t,-in[i]);
}
if(dinic() != sum){
printf("IMPOSSIBLE\n\n");
continue;
}
for(int i = 1;i<=m;i++){
for(int j = 1;j<=n;j++) {
printf("%d%c",lim[0][i][j]+e[id[i][j]].cap,j == n?'\n':' ');
}
}
}
}
3. 有源汇最大流
因为跑一次最大流跑出来的只是一个可行解,而非原图中s->t的最大流,所以去掉ss和tt,在上一次最大流跑完后的残留网络上再跑一次最大流,结果就是所要的答案,这是因为残留网络上t->s的流量被退回来,相当于是加上了可行流的流量。
例题:zoj3229
int main(){
//freopen("a.in","r",stdin);
int g,c,d,l,r;
while(~scanf("%d%d",&n,&m)){
init();
s = n + m;
t = s + 1;
for(int i = 0;i<m;i++){
scanf("%d",&g);
addedge(i+n,t,inf-g);
in[i+n] -= g;
in[t] += g;
}
for(int i = 0;i"%d%d",&c,&d);
addedge(s,i,d);
for(int j = 0;j"%d%d%d",&g,&l,&r);
addedge(i,g+n,r-l);
in[i] -= l;
in[g+n] += l;
low[i][g] = l;
id[i][g] = tot - 1;
}
}
addedge(t,s,inf);
int num = t;
s += 2;t += 2;
int sum = 0;
for(int i = 0;i<=num;i++){
if(in[i] > 0){
addedge(s,i,in[i]);
sum += in[i];
}else
addedge(i,t,-in[i]);
}
if(dinic() != sum){
printf("-1\n\n");
continue;
}
head[s] = head[t] = -1;
s -= 2;t -= 2;
printf("%d\n",dinic());
//之所以只需要再跑一次最大流,是因为第一次的流量储存在t->s中,第二次最大流会加上这一段的流量
for(int i = 0;ifor(int j = 0;j<m;j++) {
if(id[i][j]){
printf("%d\n",low[i][j] + e[id[i][j]].cap);
}
}
}
printf("\n");
}
}
4. 有源汇最小流
有人会认为使用可行流的解法跑出来的结果就是最小流,实际上并不是这样,这是因为在原图中可能存在有圈,这样的话可能不需要通过增加s->t的流量来满足圈中边的下界,因为圈中可以有环流。
求最小流一个比较巧妙的做法是增设ss和tt,但不连t->s的流,先跑一次最大流,这样可以去除上述的冗余流量(我的理解)。再连一条t->s的流,跑一次ss->tt的最大流,这时候s->t的流量(也是t->s的流的流量)即为最小流。
例题:poj3801
int main(){
//freopen("a.in","r",stdin);
char s1[5],s2[5];
int a,b,c;
while(~scanf("%d%d",&n,&m)){
if(n + m == 0) break;
init();
s = n + 1;
t = s + 1;
for(int i = 0;i<m;i++){
scanf("%s%s%d",s1,s2,&c);
a = getnum(s1);
b = getnum(s2);
addedge(a,b,inf-c);
in[a] -= c;
in[b] += c;
}
int num = t;
s += 2;t += 2;
int sum = 0;
for(int i = 0;i<=num;i++){
if(in[i] > 0){
addedge(s,i,in[i]);
sum += in[i];
}else
addedge(i,t,-in[i]);
}
addedge(t-2,s-2,inf);
if(dinic() != sum){
printf("impossible\n");
continue;
}
printf("%d\n",e[tot-1].cap);
}
}