[知识点]有上下界的网络流学习笔记

最近在做网络流。前些日子在$CDQZ$集训的时候看了看,现在来做做题。

大力推荐一发liu_runda的博客!写的太好了(其实也是我懒得写学习笔记)。

简单写几句要点吧2333

1.无源汇有上下界可行流(也就是循环流)

先以下界构图,再在残余网络中通过构建虚拟源汇调整流量,使得满足各个点流入量=流出量

每条边流量=下界+残余网络中跑出的流量

例题:[ZOJ 2314]Reactor Cooling

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <cmath>
#define pos(i,a,b) for(int i=(a);i<=(b);i++)
#define N 510000
const int inf=0x7fffffff;
int anti(int x){return (x&1)?(x+1):(x-1);}
char xB[(1<<15)+10],*xS=xB,*xT=xB;
#define gtc (xS==xT&&(xT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xT)?0:*xS++)
inline void read(int &shu){
    register char ch=gtc;int f=1;
    for(shu=0;ch<'0'||ch>'9';ch=gtc)if(ch=='-') f=-1;
    for(;ch>='0'&&ch<='9';shu=(shu<<1)+(shu<<3)+ch-'0',ch=gtc);
    shu*=f;
}
int n,m,T;
int up[N],down[N],tot[N];
struct haha{
	int next,to,w;
}edge[N];
int head[N],cnt,s,t,sum;
void add(int u,int v,int w){
	edge[++cnt].w=w;
	edge[cnt].to=v;
	edge[cnt].next=head[u];
	head[u]=cnt;
}
int dep[N];
bool bfs(){
	memset(dep,0,sizeof(dep));
	dep[s]=1;std::queue<int> q;
	q.push(s);
	while(!q.empty()){
		int k=q.front();q.pop();
		for(int i=head[k];i;i=edge[i].next){
			int to=edge[i].to,w=edge[i].w;
			if(!dep[to]&&w){
				dep[to]=dep[k]+1;
				if(to==t) return 1;
				q.push(to);
			}
		}
	}
	return 0;
}
int dfs(int x,int f){
	if(x==t) return f;
	int temp=f;
	for(int i=head[x];i;i=edge[i].next){
		int to=edge[i].to,w=edge[i].w;
		if(temp&&dep[to]==dep[x]+1&&w){
			int k=dfs(to,std::min(temp,w));
			if(!k){dep[to]=0;continue;}
			temp-=k;edge[i].w-=k;edge[anti(i)].w+=k;
		}
	}
	return f-temp;
}
void work(){
	int res(0);
	while(bfs()) res+=dfs(s,inf);
	if(res==sum){
		printf("YES\n");
		pos(i,1,m){
			printf("%d\n",down[i]+edge[i*2].w);
		}
	}
	else printf("NO\n");
}
void init(){
	memset(edge,0,sizeof(edge));
	cnt=sum=0;s=n+1;t=s+1;
	pos(i,0,N-10) tot[i]=head[i]=up[i]=down[i]=0;
}
int main(){
	read(T);
	while(T--){
		read(n);read(m);
		init();
		pos(i,1,m){
			int x,y;read(x);read(y);read(down[i]);read(up[i]);
			add(x,y,up[i]-down[i]);add(y,x,0);
			tot[y]+=down[i];tot[x]-=down[i];
		}
		pos(i,1,n){
			if(tot[i]>0) add(s,i,tot[i]),add(i,s,0),sum+=tot[i];
			else add(i,t,-tot[i]),add(t,i,0);
		}
		work();
	}
	return 0;
}

2.有源汇有上下界可行流

建$t$->$s$的边,形成无源汇的图,再跑无源汇上下界可行流,最后删去此边即可

原图中可行流的总流量=此边在无源汇图中跑出的流量

每条边流量=下界+残余网络中跑出的流量

3.有源汇有上下界最大流

跑完有源汇有上下界可行流后,按每条边剩下的流量重构原图,再跑最大流,这样保证可行同时流最大

原图中最大流总流量=可行流总流量+最大流总流量

每条边流量=下界+残余网络中跑出的流量+重构图中跑出的流量

例题:[ZOJ 3229]Shoot the Bullet

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#define pos(i,a,b) for(int i=(a);i<=(b);i++)
#define N 2010
const int inf=0x7ffffff;
int anti(int x){return (x&1)?(x+1):(x-1);}
int n,m;
int up[610000],down[600000],tot[N],sz,sz2;
struct haha{
	int next,to,w;
}edge[2][600000];
int head[2][N],cnt[2],s[2],t[2];
int ss,tt,sum,cun[600000],ge[1000][1000];
int hmin(int x,int y){return x>y?y:x;}
void add(int u,int v,int w,int k){
	edge[k][++cnt[k]].w=w;
	edge[k][cnt[k]].to=v;
	edge[k][cnt[k]].next=head[k][u];
	head[k][u]=cnt[k];
}
int dep[N];
bool bfs(int k){
	memset(dep,0,sizeof(dep));
	dep[s[k]]=1;
	std::queue<int> q;q.push(s[k]);
	while(!q.empty()){
		int kk=q.front();q.pop();
		for(int i=head[k][kk];i;i=edge[k][i].next){
			int to=edge[k][i].to,w=edge[k][i].w;
			if(!dep[to]&&w){
				dep[to]=dep[kk]+1;
				if(to==t[k]) return 1;
				q.push(to);
			}
		}
	}
	return 0;
}
int dfs(int x,int f,int k){
	if(x==t[k]) return f;
	int temp=f;
	for(int i=head[k][x];i;i=edge[k][i].next){
		int to=edge[k][i].to,w=edge[k][i].w;
		if(temp&&dep[to]==dep[x]+1&&w){
			int kk=dfs(to,hmin(temp,w),k);
			if(!kk){dep[to]=0;continue;}
			temp-=kk;edge[k][i].w-=kk;edge[k][anti(i)].w+=kk;
		}
	}
	return f-temp;
}
int ans1;
void work(){
	int res(0);
	while(bfs(0)) res+=dfs(s[0],inf,0);
	if(res==sum){
		ans1=edge[0][sz*2].w;
		pos(i,1,m){
			++sz2;
			add(n+i,t[1],up[sz2]-down[sz2]-edge[0][sz2*2].w,1);add(t[1],n+i,0,1);
		}
		pos(i,1,n){
			++sz2;
			add(s[1],i,up[sz2]-down[sz2]-edge[0][sz2*2].w,1);add(i,s[1],0,1);
			pos(j,1,ge[i][0]){
				++sz2;
				add(i,n+ge[i][j],up[sz2]-down[sz2]-edge[0][sz2*2].w,1);add(n+ge[i][j],i,0,1);
			}
		}
		while(bfs(1)) ans1+=dfs(s[1],inf,1);
		printf("%d\n",ans1);
		pos(i,1,cun[0]){
			printf("%d\n",edge[0][cun[i]*2].w+edge[1][cun[i]*2].w+down[cun[i]]);
		}
	}
	else printf("-1\n");
}
void init(){
	memset(edge,0,sizeof(edge));
	memset(head,0,sizeof(head));
	cnt[0]=cnt[1]=sz=sz2=sum=cun[0]=ans1=0;
	pos(i,0,N-10) tot[i]=0;
}
signed main(){
	while(scanf("%d%d",&n,&m)!=EOF){
		init();
		ss=n+m+1;tt=ss+2;
		s[0]=s[1]=tt+1;t[0]=t[1]=tt+2;
		pos(i,1,m){
			++sz;scanf("%d",&down[sz]);up[sz]=inf;
			add(n+i,tt,up[sz]-down[sz],0);add(tt,n+i,0,0);
			tot[tt]+=down[sz];tot[n+i]-=down[sz];
		}
		pos(i,1,n){
			scanf("%d",&ge[i][0]);
			++sz;scanf("%d",&up[sz]);down[sz]=0;
			add(ss,i,up[sz]-down[sz],0);add(i,ss,0,0);
			tot[i]+=down[sz];tot[ss]-=down[sz];
			pos(j,1,ge[i][0]){
				++sz;cun[++cun[0]]=sz;
				scanf("%d%d%d",&ge[i][j],&down[sz],&up[sz]);
				ge[i][j]++;
				add(i,n+ge[i][j],up[sz]-down[sz],0);add(n+ge[i][j],i,0,0);
				tot[n+ge[i][j]]+=down[sz];tot[i]-=down[sz];
			}
		}
		++sz;add(tt,ss,inf,0);add(ss,tt,0,0);up[sz]=inf;down[sz]=0;
		pos(i,1,tt){
			if(tot[i]>0) add(s[0],i,tot[i],0),add(i,s[0],0,0),sum+=tot[i];
			else add(i,t[0],-tot[i],0),add(t[0],i,0,0);
		}
		work();
		printf("\n");
	}
	return 0;
}

4.有源汇有上下界最小流  

跑完有源汇有上下界可行流后,按跑出的残余流量图重构原图,再从$t$->$s$跑最大流,由于反边存的是跑过的流量,这样可以最大“退回”跑过的流量

原图中最小流总流量=可行流总流量-反向跑出最大流总流量

每条边流量=下界+残余网络中跑出的流量-反向跑出的流量

例题:[BZOJ 2502]清理雪道

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#define pos(i,a,b) for(int i=(a);i<=(b);i++)
#define N 201000
const int inf=0x3fffffff;
int anti(int x){return (x&1)?(x+1):(x-1);}
char xB[(1<<15)+10],*xS=xB,*xT=xB;
#define gtc (xS==xT&&(xT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xT)?0:*xS++)
inline void read(int &shu){
    register char ch=gtc;int f=1;
    for(shu=0;ch<'0'||ch>'9';ch=gtc) if(ch=='-') f=-1;
    for(;ch>='0'&&ch<='9';shu=(shu<<1)+(shu<<3)+ch-'0',ch=gtc);
    shu*=f;
}
int n;
struct haha{
	int next,to,w;
}edge[2][N];
int head[2][N],cnt[2];
void add(int u,int v,int w,int k){
	edge[k][++cnt[k]].w=w;edge[k][cnt[k]].to=v;
	edge[k][cnt[k]].next=head[k][u];head[k][u]=cnt[k];
}
int s,t,ss,tt;
int sz,sz2,up[N],down[N],sum;
int ge[500][500];
int ans,dep[N];
bool bfs(int k,int s1,int t1){
	std::queue<int> q;q.push(s1);
	memset(dep,0,sizeof(dep));
	dep[s1]=1;
	while(!q.empty()){
		int kk=q.front();q.pop();
		for(int i=head[k][kk];i;i=edge[k][i].next){
			int to=edge[k][i].to,w=edge[k][i].w;
			if(!dep[to]&&w){
				dep[to]=dep[kk]+1;
				if(to==t1) return 1;
				q.push(to);
			}
		}
	}
	return 0;
}
int dfs(int x,int t1,int f,int k){
	if(x==t1) return f;
	int temp=f;
	for(int i=head[k][x];i;i=edge[k][i].next){
		int to=edge[k][i].to,w=edge[k][i].w;
		if(w&&temp&&dep[to]==dep[x]+1){
			int kk=dfs(to,t1,std::min(temp,w),k);
			if(!kk){
				dep[to]=0;continue;
			}
			temp-=kk;edge[k][i].w-=kk;edge[k][anti(i)].w+=kk;
		}
	}
	return f-temp;
}
int tot[N];
void work(){
	int res(0);
	while(bfs(0,ss,tt)) res+=dfs(ss,tt,inf,0);
	ans+=edge[0][sz*2].w;
	pos(i,1,n){
		++sz2;add(s,i,edge[0][sz2*2-1].w,1);add(i,s,edge[0][sz2*2].w,1);
		++sz2;add(i,t,edge[0][sz2*2-1].w,1);add(t,i,edge[0][sz2*2].w,1);
	}
	pos(i,1,n){
		pos(j,1,ge[i][0]){
			++sz2;add(i,ge[i][j],edge[0][sz2*2-1].w,1);add(ge[i][j],i,edge[0][sz2*2].w,1);
		}
	}
	while(bfs(1,t,s)) ans-=dfs(t,s,inf,1);
	printf("%d\n",ans);
}
int main(){
	read(n);s=n+1;t=s+1;ss=t+1;tt=ss+1;
	pos(i,1,n){
		++sz;up[sz]=inf;add(s,i,inf,0);add(i,s,0,0);
		++sz;up[sz]=inf;add(i,t,inf,0);add(t,i,0,0);
	}
	pos(i,1,n){
		read(ge[i][0]);
		pos(j,1,ge[i][0]){
			read(ge[i][j]);
			++sz;up[sz]=inf;down[sz]=1;add(i,ge[i][j],up[sz]-down[sz],0);add(ge[i][j],i,0,0);
			tot[ge[i][j]]+=down[sz];tot[i]-=down[sz];
		}
	}
	++sz;up[sz]=inf;add(t,s,inf,0);add(s,t,0,0);
	pos(i,1,t){
		if(tot[i]>0) add(ss,i,tot[i],0),add(i,ss,0,0);
		else if(tot[i]<0) add(i,tt,-tot[i],0),add(tt,i,0,0);
	}
	work();
	return 0;
}

5.有源汇有上下界最小费用可行流

在跑可行流的时候,因为跑满流即存在可行解,而满流方案有很多种,我们要求的是[可行]->[最小费用],所以跑最小费用最大流即可

最小费用=有费用的边*容量下界+跑出来的最小费用

例题:[BZOJ 2055]80人环游世界

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <queue>
using namespace std;
#define pos(i,a,b) for(int i=(a);i<=(b);i++)
#define N 101000
const int inf=0x7f7f7f7f;
int anti(int x){return (x&1)?(x+1):(x-1);}
char xB[(1<<15)+10],*xS=xB,*xT=xB;
#define gtc (xS==xT&&(xT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xT)?0:*xS++)
inline void read(int &shu){
    register char ch=gtc;int f=1;
    for(shu=0;ch<'0'||ch>'9';ch=gtc) if(ch=='-') f=-1;
    for(;ch>='0'&&ch<='9';shu=(shu<<1)+(shu<<3)+ch-'0',ch=gtc);
    shu*=f;
}
int n,m,ss,tt,s,t,pre;
int V[N],tot[N],up[N],down[N];
int sz,C[500][500];
struct haha{
	int next,to,w,c;
}edge[N];
int head[N],cnt;
void add(int u,int v,int w,int c){
	edge[++cnt].w=w;edge[cnt].to=v;
	edge[cnt].c=c;edge[cnt].next=head[u];
	head[u]=cnt;
}
int dis[N],flag[N],from[N];
int spfa(){
	pos(i,0,N-10) dis[i]=inf,flag[i]=from[i]=0;
	std::queue<int> q;dis[ss]=0;flag[ss]=1;q.push(ss);
	while(!q.empty()){
		int k=q.front();q.pop();
		for(int i=head[k];i;i=edge[i].next){
			int to=edge[i].to,w=edge[i].w,c=edge[i].c;
			if(w&&dis[to]>dis[k]+c){
				dis[to]=dis[k]+c;
				if(!flag[to]){flag[to]=1;q.push(to);}
				from[to]=i;
			}
		}
		flag[k]=0;
	}
	return dis[tt]==inf?(-1):dis[tt];
}
int cost(){
	int p=tt,f=inf;
	while(p!=ss){
        f=std::min(f,edge[from[p]].w);
        p=edge[anti(from[p])].to;
    }
    p=tt;
    while(p!=ss){
        edge[from[p]].w-=f;
        edge[anti(from[p])].w+=f;
        p=edge[anti(from[p])].to;
    }
    return f;
}
void work(){
	int res(0),l;
	while(1){
		l=spfa();if(l==-1) break;
		res+=l*cost();
	}
	printf("%d\n",res);
}
int main(){
	read(n);read(m);
	s=n*2+1,t=s+1;pre=t+1;ss=pre+1;tt=ss+1;
	pos(i,1,n){ 
		read(V[i]);
		++sz;up[sz]=V[i];down[sz]=V[i];
		add(i,n+i,0,0);add(n+i,i,0,0);
		tot[n+i]+=down[sz];tot[i]-=down[sz];
		++sz;up[sz]=inf;
		add(pre,i,inf,0);add(i,pre,0,0);
		++sz;up[sz]=inf;
		add(n+i,t,inf,0);add(t,n+i,0,0);
	}
	pos(i,1,n){
		pos(j,i+1,n){
			read(C[i][j]);if(C[i][j]==-1) continue;
			++sz;up[sz]=inf;
			add(n+i,j,inf,C[i][j]);add(j,n+i,0,-C[i][j]);
		}
	}
	++sz;up[sz]=m;down[sz]=m;
	add(s,pre,0,0);add(pre,s,0,0);
	tot[pre]+=down[sz];tot[s]-=down[sz];
	++sz;up[sz]=inf;
	add(t,s,inf,0);add(s,t,0,0);
	pos(i,1,pre){
		if(tot[i]>0) add(ss,i,tot[i],0),add(i,ss,0,0);
		else add(i,tt,-tot[i],0),add(tt,i,0,0);
	}
	work();
	return 0;
}

--------------------------------以下部分仅为YY而来,并没有打题验证OvO----------------------------------  

6.有源汇有上下界最X费用最X流

由于要满足[最X流]->[最X费用],所以我们先按最X费用跑出可行流,再满足最X流且最X费用

掌握一个原则:最大流要尽量多跑,最小流要尽量多“退回”,所以参照上面的方法重构图或者反向跑即可

例如:最大费用最小流

由于要最小流,我们先跑最大费用最大流使得满流达到可行的效果,然后反向跑最小费用最大流,使得尽量多“退回”流的同时尽量少“退回”费用

其他类比即可OvO

这几天一直在打网络流,对于有上下界的网络流做了一次小小的总结,希望能对自己有所提升,同时希望能够帮助到别人

posted @ 2018-01-17 18:56 Hallmeow 阅读(...) 评论(...) 编辑 收藏