有上下界的网络流问题
前几天就想写了的,一直没写,今天就写完吧。
因为在做这些上下界的题的时候,遇到了很多问题,在大神的帮助下还是一一解决了的。(英文没学好诶喂,,在sgu和poj各种wa。。)
主要是没看题,求上下界已经理解了的。。
分3种上下界网络流问题:(在本文只说做法和一些相关的东西,证明和推导请看后面写出的参考)
无源无汇的上下界最大流问题(没有源和汇我的理解,即流量只需满足流的3个性质即可,没有从源点流出从汇流进的约束):
做法:将图的下界分离到一个附加源和汇中,而上界则变为原弧的上界减去下界的差,构成一个附加网络,再在附加源汇上跑一次最大流即可。其中,记录每个点的入流下界和-出流下界和,当下界和>0时怎连入一条源s到这个点的边,上界为这个下界和,下界为0;当下界和<0则连入一条这个点到汇t的边,上界为下界和的绝对值,下界为0。
判断:仅当以源s为起点的弧或以汇t为终点的弧全部流满,则满足下界的可行流才存在,否则不存在。
例题:sgu194
代码:
#include <cstdio>
#include <cstring>
using namespace std;
#define CC(a, c) memset(a, c, sizeof(a))
#define FOR(i,a,n) for(i=a;i<=n;++i)
const int maxn=500, oo=~0u>>1, maxm=80810;
int S, T, cap[maxm], d[maxn], cur[maxn], gap[maxn], p[maxn];
int N, ihead[maxn], inext[maxm], to[maxm], from[maxm], cnt;
int dn[maxm], du[maxn];
int min(const int& a, const int& b) { return a < b ? a : b; }
int isap() {
int flow=0, u, v, i, f=oo;
CC(d, 0); CC(gap, 0);
for(i=1; i<=N; ++i) cur[i]=ihead[i];
u=S; gap[0]=N;
while(d[S]<N) {
for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]==d[to[i]]+1) break;
if(i) {
cur[u]=i; v=to[i]; f=min(f, cap[i]); p[v]=i; u=v;
if(v==T) {
for(; v!=S; v=from[p[v]]) cap[p[v]]-=f, cap[p[v]^1]+=f;
u=S; flow+=f; f=oo;
}
}
else {
if( !(--gap[d[u]]) ) break;
d[u]=N;
cur[u]=ihead[u];
for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]>d[to[i]]+1) d[u]=d[to[i]]+1;
gap[d[u]]++; if(u!=S) u=from[p[u]];
}
}
return flow;
}
void add(int u, int v, int c) {
inext[++cnt]=ihead[u]; ihead[u]=cnt; from[cnt]=u; to[cnt]=v; cap[cnt]=c;
inext[++cnt]=ihead[v]; ihead[v]=cnt; from[cnt]=v; to[cnt]=u; cap[cnt]=0;
}
void init(int s, int t, int n) {
S=s; T=t; N=n; cnt=1;
CC(ihead, 0); CC(du, 0); CC(dn, 0);
}
int main() {
int n, m, u, v, down, up, i, flag;
while(~scanf("%d%d", &n, &m)) {
init(n+1, n+2, n+2);
for(i=1; i<=m; ++i) {
scanf("%d%d%d%d", &u, &v, &down, &up);
add(u, v, up-down);
du[u]-=down; du[v]+=down; //入流就+,出流就-
dn[i]=down; //记录下界,因为最后这些边上的流量是不包括下界的,因此最后要加回来
}
for(i=1; i<=n; ++i) {
if(du[i]>0) add(S,i,du[i]); //>0就从源s连一条弧
if(du[i]<0) add(i,T,-du[i]); //<0就连一条弧到汇t
}
isap();
flag=1;
for(i=ihead[S]; i; i=inext[i]) if(cap[i]) { flag=0; break; } //仅当流满才存在
if(!flag) puts("NO");
else {
puts("YES");
for(i=1; i<=m; ++i)
printf("%d\n",cap[(i<<1)+1]+dn[i]); //加回下界
}
}
return 0;
}
有源汇的上下界最大流问题:
做法:从汇t连一条弧到源s,上界为无限大,下界为0。再设一个超级源ss和一个超级汇tt,按照无源汇上下界最大流的方法连边给ss和tt,从ss跑最大流到tt,判断是否有弧满后,去掉超级源和汇及其边,跑一次s-t的最大流(注意这里最大流绝不是t-s的反向弧,因为在第二次跑最大流的时候将这弧退掉了),最后根据题目要求输出即可。
技巧:我用的是链式前向星(邻接表),因此将ss和tt的head去掉即可,为什么呢,因为这不会影响到后面跑最大流,因为ss和tt本来就不在s-t的增广路上,但这里要注意,因为ss和tt还在图中,因此调用最大流的算法时,点的数量要包括了ss和tt。
例题:zoj3229
ps:这题是我调试了2天的。。太坑了,很多细节,还是注意看题吧。。比如说按输入的顺序输出。。导致我算法没错则一直在找算法的错误,还是请大神帮我找出来的。。。。。但是还是找到了一些bug且大神帮我优化了我的sap的一个地方,还是很感谢他的~
ps:这题样例是有问题的,请不要相信第一个case,用第二个case来做测试吧
代码:
#include <cstdio>
#include <cstring>
using namespace std;
#define CC(a, c) memset(a, c, sizeof(a))
#define FOR(i,a,n) for(i=a;i<=n;++i)
const int maxn=1510, oo=100000000, maxm=151000;
int cap[maxm], d[maxn], cur[maxn], gap[maxn], p[maxn];
int ihead[maxn], inext[maxm], to[maxm], from[maxm], cnt;
int inout[maxn], down[400][1200], id[400][1200]; //这里用maxn*maxn的话就mle了。。
int E[maxm][2], e;
int min(const int& a, const int& b) { return a < b ? a : b; }
int isap(int s, int t, int n) {
int flow=0, i, u, f=oo;
CC(gap, 0); CC(d, 0);
FOR(i, 0, n) cur[i]=ihead[i];
u=s; gap[0]=n;
while(d[s]<n) {
for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]==d[to[i]]+1) break;
if(i) {
cur[u]=i; p[to[i]]=i; u=to[i];
if(u==t) {
for(f=oo; u!=s; u=from[p[u]]) f=min(f, cap[p[u]]); //这里的优化是大神教的
for(u=t; u!=s; u=from[p[u]]) cap[p[u]]-=f, cap[p[u]^1]+=f;
flow+=f;
}
}
else {
if( !(--gap[d[u]]) ) break;
d[u]=n;
cur[u]=ihead[u];
for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]>d[to[i]]+1)
d[u]=d[to[i]]+1;
gap[d[u]]++; if(u!=s) u=from[p[u]];
}
}
return flow;
}
void add(int u, int v, int c) {
inext[++cnt]=ihead[u]; ihead[u]=cnt; from[cnt]=u; to[cnt]=v; cap[cnt]=c;
inext[++cnt]=ihead[v]; ihead[v]=cnt; from[cnt]=v; to[cnt]=u; cap[cnt]=0;
}
void init() {
CC(ihead, 0); cnt=1; CC(down, 0); CC(id, 0); CC(inout, 0);
e=0;
}
int main() {
int n, m, i, g, d, t, l, r, sum, S, T;
while(~scanf("%d%d", &n, &m)) {
init();
S=n+m+1; T=n+m+2;
FOR(i, 1, m) {
scanf("%d", &g);
add(i+n, T, oo-g);
inout[i+n]-=g; inout[T]+=g;
}
FOR(i, 1, n) {
scanf("%d%d", &g, &d);
add(S, i, d);
while(g--) {
scanf("%d%d%d", &t, &l, &r);
inout[i]-=l; inout[t+1+n]+=l;
add(i, t+1+n, r-l);
down[i][t+1]+=l; id[i][t+1]=cnt; //记录下标,反向弧,因为用的是残量网络跑的最大流,所以最后应该是反向弧
E[++e][0]=i; E[e][1]=t+1; //记录顺序好输出
}
}
add(T, S, oo); //添加汇到源的oo弧
S=n+m+3; T=n+m+4; //设置超级源和汇
sum=0;
FOR(i, 1, n+m+2) {
if(inout[i]>0) { sum+=inout[i]; add(S, i, inout[i]); } //sum是判断满流的一种方法
if(inout[i]<0) add(i, T, -inout[i]);
}
if(isap(S, T, T)!=sum) printf("-1\n");
else {
ihead[S]=ihead[T]=0; //去掉超级源和汇的弧,注意,这里只是删掉点,也就是说,反向弧还存在。但是反向弧不会影响最大流,因为它不在增广路上面
S=n+m+1; T=n+m+2; //恢复源和汇
printf("%d\n", isap(S, T, T+2)); //打印最大流,这里要注意,因为点集里还有超级源和汇,所以N要包括那2个点
FOR(i, 1, e)
printf("%d\n", down[ E[i][0] ][ E[i][1] ]+cap[id[ E[i][0] ][ E[i][1]] ]);
}
printf("\n");
}
return 0;
}
有源汇的上下界最小流问题:
做法:先不连边t-s,先构造了附加网络到超级源和超级汇中,然后跑超级源到超级汇的最大流,再连汇t到源s上界为无限大下界为0的弧,再跑一次超级源到超级汇的最大流即可,最小流就是t-s的反向弧。。。(ps,这里我不明白为什么这样做,问了之前帮我的大神,他也不知道,因此我先放下吧,以后问问其它大神。。其实我有一个理解,就是先将之前 上界-下界 的那些弧的最大跑出来, 然后连边后再跑,其实就是退流,将之前跑的退回去,这样既满足了下界限制,又是可行流,因为退流回去的是最大流,因此第二次跑出来的是最小流)
例题:sgu176
代码:
#include <cstdio>
#include <cstring>
using namespace std;
#define CC(a, c) memset(a, c, sizeof(a))
#define FOR(i,a,n) for(i=a;i<=n;++i)
const int maxn=1510, oo=100000000, maxm=151000;
int cap[maxm], d[maxn], cur[maxn], gap[maxn], p[maxn];
int ihead[maxn], inext[maxm], to[maxm], from[maxm], cnt;
int inout[maxn], ans[maxm], id[maxm];
int min(const int& a, const int& b) { return a < b ? a : b; }
int isap(int s, int t, int n) {
int flow=0, i, u, f=oo;
CC(gap, 0); CC(d, 0);
FOR(i, 0, n) cur[i]=ihead[i];
u=s; gap[0]=n;
while(d[s]<n) {
for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]==d[to[i]]+1) break;
if(i) {
cur[u]=i; p[to[i]]=i; u=to[i];
if(u==t) {
for(f=oo; u!=s; u=from[p[u]]) f=min(f, cap[p[u]]);
for(u=t; u!=s; u=from[p[u]]) cap[p[u]]-=f, cap[p[u]^1]+=f;
flow+=f;
}
}
else {
if( !(--gap[d[u]]) ) break;
d[u]=n;
cur[u]=ihead[u];
for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]>d[to[i]]+1)
d[u]=d[to[i]]+1;
gap[d[u]]++; if(u!=s) u=from[p[u]];
}
}
return flow;
}
void add(int u, int v, int c, int _id) {
inext[++cnt]=ihead[u]; ihead[u]=cnt; from[cnt]=u; to[cnt]=v; cap[cnt]=c; id[cnt]=_id;
inext[++cnt]=ihead[v]; ihead[v]=cnt; from[cnt]=v; to[cnt]=u; cap[cnt]=0; id[cnt]=0;
}
void init() {
CC(ihead, 0); cnt=1; CC(ans, 0); CC(id, 0); CC(inout, 0);
}
int main() {
int n, m, i, S, T, E, ss, tt;
while(~scanf("%d%d", &n, &m)) {
init();
S=1; T=n;
FOR(i, 1, m) {
int u, v, c, k;
scanf("%d%d%d%d", &u, &v, &c, &k);
if(k) inout[u]-=c, inout[v]+=c, ans[i]=c;
else add(u, v, c, i);
}
E=cnt;
ss=n+1; tt=ss+1;
FOR(i, 1, n) {
if(inout[i]>0) add(ss, i, inout[i], 0);
if(inout[i]<0) add(i, tt, -inout[i], 0);
}
isap(ss, tt, tt); //先跑一次最大流
add(T, S, oo, 0); //连边
isap(ss, tt, tt); //再跑一次最大流
for(i=ihead[ss]; i; i=inext[i]) if(cap[i]) break;
if(i) puts("Impossible");
else {
int res=0;
for(i=ihead[T]; i; i=inext[i]) if(to[i]==S) break;
res=cap[i^1];
printf("%d\n", res);
FOR(i, 2, E) ans[id[i]]=cap[i^1];
FOR(i, 1, m) if(i!=m) printf("%d ", ans[i]);
else printf("%d\n", ans[i]);
}
}
return 0;
}
参考:
周源《一种简易的方法求解流量有上下界的网络中网络流问题》
Mr. Ant博客:http://www.cnblogs.com/kane0526/archive/2013/04/05/3001108.html

浙公网安备 33010602011771号