【瞎口胡】上下界网络流

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

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

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

无源汇上下界可行流#

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

对于第 i 条边 (xi,yi,(li,ri)) 至少需要流 li 的流量,我们不妨将这部分流量流完,然后建立一个「残」网络,该网络中第 i 条边为 (xi,yi,rili)

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

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

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

正确性显然。

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

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

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

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

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

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

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

题意

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

1n300,1m5000,边权 wi 满足 1wi300

题解

每条边至少经过一次等价于至少流过 1 的流量,因此边 (u,v,w) 在网络流图中表示为 (u,v,(1,+),w)。路径可以在任何点结束,于是建立超级汇点 T(注意与虚拟汇点 T 区分,它们不是同一个点),并对于每个点 i 连接边 (i,T,(0,+),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 和所有与它们相连的边删掉,再跑一遍 ST 的(最小费用)最大流「榨干」剩下的流量即可。最后和可行流的流量相加。

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

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

考虑反向榨干,从 TS 跑最大流,然后用可行流的流量减掉。

作者:Meatherm

出处:https://www.cnblogs.com/Meatherm/p/16050733.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Meatherm  阅读(64)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示