上下界网络流小记

Introduction

上下界网络流,就是每条边都有一个流量上界和流量下界。

Solution

无源汇有上下界可行流

例题

由于没有源点和汇点,因此对于每个点都需要满足:流入这个点的流量总量 \(=\) 流出这个点的流量总量。我们将其称作流量平衡。

我们尝试将问题简化一下。设第 \(i\) 条边的流量范围为 \([L_i,R_i]\)。我们先将每一条边都填充上 \(L_i\) 的流量,这样每条边就都满足流量限制了。可是这样并不能满足流量平衡:可能有些流量在流入某个点之后就凭空消失了,也可能有某个点流出了不应该存在的流量。设 \(f_i\) 表示第 \(i\) 个点有多少不平衡的流量。问题转化为:每个点需要 \(f_i\) 的流量(如果 \(f_i<0\) 就表示需要流入,\(f_i>0\) 表示需要流出),每条边有一个流量限制 \([0,R_i-L_i]\)。需要一些边填充一些流量使得每个点都流量平衡。

我们可以建立一个虚拟源点 \(S\) 和虚拟汇点 \(T\),对于 \(f_i>0\) 的点连一条由 \(S\)\(i\),流量上界为 \(f_i\) 的边;对于 \(f_i<0\) 的点连一条由 \(i\)\(T\) 的流量上界为 \(-f_i\) 的边。这样一来,我们就可以将需要的流量看作是 \(S\) 给的,而多出来的流量需要还给 \(T\)。跑一遍从 \(S\)\(T\) 的最大流,如果所有流出 \(S\) 的边都是满流状态,就说明存在可行流,一组解就是此时各条边的实际流量。否则无解。

int main()
{
	scanf("%d%d",&n,&m);
	st=n+1,ed=n+2;
	alen=1;memset(last,0,sizeof(last));
	for(int i=1;i<=m;i++)
	{
		int x,y,u,v;
		scanf("%d%d%d%d",&x,&y,&u,&v);
		ins(x,y,v-u),ins(y,x,0);
		f[x]-=u,f[y]+=u;
		lower[i]=u;
	}
	int sum_out=0,sum_in=0;
	for(int i=1;i<=n;i++)
	{
		if(f[i]>0) ins(st,i,f[i]),ins(i,st,0),sum_out+=f[i];
		if(f[i]<0) ins(i,ed,-f[i]),ins(ed,i,0);
	}
	while(bfs())
	{
		memcpy(cur,last,sizeof(last));
		sum_in+=Dinic(st,INF);
	}
	if(sum_in^sum_out) puts("NO");
	else
	{
		puts("YES");
		for(int i=1;i<=m;i++) printf("%d\n",lower[i]+a[i*2+1].c);
	}
	return 0;
}

有源汇上下界可行流

增加了一个源点和一个汇点。由于需要满足源点流出的流量和汇点流入的流量相等,我们可以连一条从汇点到源点且流量上限为正无穷的边,这样就转化为无源汇上下界可行流的问题了。最后由汇点连向源点的边的实际流量便是答案。

有源汇上下界最大流

例题
虽然我们找到了可行流,但是这还并不一定是最大流。这是因为跑可行流时只有与汇点或汇点相连的边是满流的,其他边的流量可能还有一定的增加空间。于是,我们可以在残量网络上再跑一次由真正的源点到真正的汇点的最大流,两次的结果相加之后就是真正的答案了。注意在跑第二次之前要先删除所有后面添加的边。

如果使用 Dinic 算法,在实现过程中,我们可以不用删边,直接在跑完第一次最大流之后的残量网络上跑第二次最大流,但是要先把答案清零。这是因为在跑完第一次最大流之后,连接汇点和源点的边的反向边上增加的流量正好等于第一次跑出来的最大流。而在第二次跑的过程中,一定会有一部分流量由这条边直接流向汇点,其效果便是加上了第一次的答案。

有源汇上下界最小流

例题

找到可行流之后,我们发现有些流量并不是必需的。于是我们可以像上下界最大流一样,在跑完第一遍最大流之后再跑一遍从真正汇点到真正源点的最大流,这样就找到了哪些流量可以退回的。第一次的最大流减去第二次的便是最终答案了。

int main()
{
	scanf("%d%d%d%d",&n,&m,&st,&ed);
	S=n+1,T=n+2;
	alen=1;memset(last,0,sizeof(last));
	for(int i=1;i<=m;i++)
	{
		int x,y,u,v;
		scanf("%d%d%d%d",&x,&y,&u,&v);
		ins(x,y,v-u),ins(y,x,0);
		f[x]-=u,f[y]+=u;
	}
	int sum1=0,sum2=0;
	for(int i=1;i<=n;i++)
	{
		if(f[i]>0) ins(S,i,f[i]),ins(i,S,0),sum1+=f[i];
		if(f[i]<0) ins(i,T,-f[i]),ins(T,i,0);
	}
	ins(ed,st,INF),ins(st,ed,0);
	while(bfs())
	{
		memcpy(cur,last,sizeof(last));
		sum2+=Dinic(S,INF);
	}
	if(sum1^sum2) puts("please go home to sleep");
	else
	{
		for(int k=last[S];k;k=a[k].pre) a[k].c=a[k^1].c=0;
		for(int k=last[T];k;k=a[k].pre) a[k].c=a[k^1].c=0;
		sum2=a[alen].c;
		a[alen].c=a[alen-1].c=0;
		S=ed,T=st;int sum3=0;
		while(bfs())
		{
			memcpy(cur,last,sizeof(last));
			sum3+=Dinic(S,INF);
		}
		printf("%d\n",sum2-sum3);
	}
	return 0;
}

送一点疑问

练习

Luogu P5192

solution

将每一天和每一位少女都看成一个点,建立一个源点 \(st\) 和 汇点 \(ed\)。对于每个少女 \(x\) 向汇点连一条下限为 \(G_x\),上限为 \(INF\) 的边。对于每一天 \(i\),从源点出发连一条下限为 \(0\),上限为 \(D_i\) 的边。对于每一天对应的 \(T,L,R\),直接向对应的少女连边即可。

posted @   __Star_Sky  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
点击右上角即可分享
微信分享提示