Loading

【瞎口胡】上下界网络流

在部分网络流问题中,会要求边的流量在一个区间 \([l,r]\) 之间,即新增了一个对流量下界的限制。

这种问题被称为「上下界网络流」。

如果你不知道什么是网络流,我建议你马上去读这里

无源汇上下界可行流

求出一组流 \(f\) 使得每个点流量平衡。\(i\) 点是流量平衡的,当且仅当 \(\sum\limits_{j\neq i} f(j,i) = \sum \limits_{j\neq i} f(i,j)\),即流入流量等于流出流量。

对于第 \(i\) 条边 \((x_i,y_i,(l_i,r_i))\) 至少需要流 \(l_i\) 的流量,我们不妨将这部分流量流完,然后建立一个「残」网络,该网络中第 \(i\) 条边为 \((x_i,y_i,r_i-l_i)\)

观察到,如果所有点流量平衡,那么我们就找到了一组可行流。反之,设每个点的净流入量(流入 - 流出)为 \(s_i\),那么:

  • 如果 \(s_i=0\),那么该点流量平衡,我们不用处理这个点。
  • 如果 \(s_i>0\),说明流入的流量过多,我们需要从该点流出 \(s_i\) 的流量到其它点。因此,我们需要一个在残网络上的虚拟源点 \(S'\),然后从 \(S'\) 流出 \(s_i\) 的流量到 \(i\)
  • 如果 \(s_i<0\),说明流出的流量过多,我们需要从其它点接受 \(s_i\) 的流量。因此,我们需要一个在残网络上的虚拟汇点 \(T'\),然后从该点流 \(s_i\) 的流量到 \(T'\)

最后,在残网络上跑一遍 \(S'\)\(T'\) 的最大流,满流(\(S'\) 流出的流量和 \(T'\) 流入的流量相等)则有一组可行流,否则不存在可行流。

正确性显然。

无源汇上下界最小费用可行流

把最大流改为费用流即可。

总费用即为费用流的费用加上流满每条边下界的费用。

有源汇上下界(最小费用)可行流

区别于无源汇的上下界网络流,有源汇上下界网络流对于源汇 \(S,T\),只需要满足 \(S\) 的净流出量等于 \(T\) 的净流入量。其它点仍然需要流量平衡。

一种做法是连一条 \((T,S,(0,+\infty))\) 的边,然后就变为了无源汇上下界网络流。\(S\)\(T\) 的流量就是这条边流过的流量。

例题 1 [AHOI2014/JSOI2014]支线剧情

题意

给定 \(n\) 个点和 \(m\) 条边的带权有向图,保证从 \(1\) 开始可以走到全部 \(n\) 个点。每次可以选择一条从 \(1\) 开始的路径,给路径上的每一条边打上一个标记,代价为路径上的边权之和。询问给每条边都打上标记的最小代价。

\(1 \leq n \leq 300,1 \leq m \leq 5000\),边权 \(w_i\) 满足 \(1 \leq w_i \leq 300\)

题解

每条边至少经过一次等价于至少流过 \(1\) 的流量,因此边 \((u,v,w)\) 在网络流图中表示为 \((u,v,(1,+\infty),w)\)。路径可以在任何点结束,于是建立超级汇点 \(T\)(注意与虚拟汇点 \(T'\) 区分,它们不是同一个点),并对于每个点 \(i\) 连接边 \((i,T,(0,+\infty),0)\)

然后就是一个源点为 \(1\) 汇点为 \(T\) 的有源汇上下界最小费用可行流。

# include <bits/stdc++.h>

const int N=100010,MAXN=1010,INF=0x3f3f3f3f;

struct Edge{
	int to,next,v,w;
};
int fsum[MAXN],coss;

struct Dinic{
	Edge edge[N];
	int head[MAXN],sum;
	bool vis[MAXN];
	int dis[MAXN],s,t,cost;
	Dinic(){
		sum=1;
		return;
	}
	inline void add(int x,int y,int v,int w){
		edge[++sum]=(Edge){y,head[x],v,w},head[x]=sum;
		return;
	}
	inline void addedge(int x,int y,int v,int w){
		add(x,y,v,w),add(y,x,0,-w);
		return;
	}
	inline bool spfa(void){
		std::queue <int> q; 
		memset(dis,INF,sizeof(dis)),dis[s]=0;
		q.push(s);
		while(!q.empty()){
			int i=q.front();
			q.pop(),vis[i]=false;
			for(int j=head[i];j;j=edge[j].next){
				int to=edge[j].to;
				if(edge[j].v&&dis[to]>dis[i]+edge[j].w){
					dis[to]=dis[i]+edge[j].w;
					if(!vis[to])
						q.push(to),vis[to]=true; 
				}
			}
		}
		return dis[t]<INF;
	}
	int dfs(int i,int flow){
		if(i==t)
			return flow;
		vis[i]=true;
		int maxsum=0;
		for(int j=head[i];j;j=edge[j].next){
			int to=edge[j].to;
			if(vis[to]||!edge[j].v||dis[to]!=dis[i]+edge[j].w)
				continue;
			int res=dfs(to,std::min(edge[j].v,flow));
			flow-=res,maxsum+=res,edge[j].v-=res,edge[j^1].v+=res,cost+=edge[j].w*res;
			if(!flow)
				break;
		}
		vis[i]=false;
		if(!maxsum)
			dis[i]=INF;
		return maxsum;
	}
	inline void solve(void){
		while(spfa()){
			dfs(s,INF);
		}
		return;
	}
}G;
int n;

inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-')f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}

int main(void){
	n=read();
	for(int i=1;i<=n;++i){
		int d=read();
		while(d--){
			int to=read(),ti=read();
			G.addedge(i,to,INF,ti);
			++fsum[to],--fsum[i],coss+=ti;
		}
	}
    // n+1 S'
    // n+2 T'
    // n+3 T
	G.s=n+1,G.t=n+2;
	for(int i=1;i<=n;++i)
		G.addedge(i,n+3,INF,0);
	G.addedge(n+3,1,INF,0); // 循环边
	for(int i=1;i<=n;++i){
		if(fsum[i]>0){
			G.addedge(G.s,i,fsum[i],0);
		}else if(fsum[i]<0){
			G.addedge(i,G.t,-fsum[i],0);
		}
	}
	G.solve();
	printf("%d",coss+G.cost);
	return 0;
}

有源汇上下界(最小费用)最大流

首先跑出一组可行流。如果它不存在,那么无解。

这个时候残量网络一定是流量平衡的。我们把 \(S',T'\) 和所有与它们相连的边删掉,再跑一遍 \(S\)\(T\) 的(最小费用)最大流「榨干」剩下的流量即可。最后和可行流的流量相加。

当然实现的时候不删也是可以的,入度为 \(0\)\(S'\) 和出度为 \(0\)\(T'\) 显然也不会影响流量...

有源汇上下界(最小费用)最小流

考虑反向榨干,从 \(T\)\(S\) 跑最大流,然后用可行流的流量减掉。

posted @ 2022-03-24 17:18  Meatherm  阅读(62)  评论(0编辑  收藏  举报