[学习笔记] 带上下界的网络流

发现竟然有这么有趣的东西,来补一发题解。

无源汇上下界可行流

我们先从它开始,现在有这样一个问题:给定一个无源汇的网络流,每个点都需要满足 流量平衡 ,也就是流出的流量必须等于流入的流量。每一条边都有一个限制 \([l_i,r_i]\) ,要求这条边的流量在这个范围内,求一个可行流。

首先有一个骚操作,由于网络流只能处理诸如 \([0,x]\) 的限制,所以我们把每条边的限制变成 \([0,r_i-l_i]\) 然后套普通的网络流即可,也就是我们先默认了 \(l_i\) 的流量,再让他去选择在此基础上多多少流量

但是显然会有问题,因为单纯这样做的话并没有考虑原有的 \(l_i\) 的流量,由于这些流量没有考虑我们使用 补流 的思想,尝试把原来的流量补到这个图中。可以兴建源点和汇点,设 \(in[i]\) 为流入 \(i\) 边的 \(l\) 之和,\(out[i]\) 为流出 \(i\) 边的 \(l\) 之和。如果 \(in[i]>out[i]\) ,说明基础流量中 总体上 是流入了 \(in[i]-out[i]\) 的流量,让他连接源点;否则说明总体上是流出了 \(out[i]-in[i]\) 的流量,让他连接汇点。

然后对建出来的图跑最大流,需要验证这个图有没有满流,也就是源点连出去的边有没有满流,如果满足这个条件那么汇点连的边也是满流的(流量平衡),这样的话基础流量就可以保证,然后剩下的边就可以随心所欲了。

有源汇上下界最大流

首先你要知道 有源汇上下界可行流 怎么做。

有一个小 \(\tt trick\) ,因为源汇点我们要兴建,所以原来的源汇点是不可以有的。关键的问题在于流量平衡,为了让原来的源汇点流量平衡,我们连一条 \((t,s,inf)\) 的边表示 \(s->t\) 的流量全部返还回来,就变成了无源汇的问题。

然后现在要最大流又怎么办呢?先求出一个可行流,然后我们要对那些原来随心所欲的边搞点事情。我们去掉新加的边 \((t,s,inf)\) ,然后再对 \(s->t\) 跑一遍最大流,因为还可以榨干这些随心所欲的边,答案是第一次最大流新加边的流量(原来的最大流又返还回去了) \(+\) 第二次的最大流。

为什么可以直接加上第二次的最大流呢?因为我们去掉了 \(t->s\) 这条返还流量的边,如果我们增加 \(s->t\) 的流量,那么在我们新建的图中是可以返还回去的,也就是用那条边来使得流量环流。

给出一道例题:[模板]有源汇上下界最大流

#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int M = 2005;
const int inf = 0x3f3f3f3f;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,tot,f[M],s1,t1,s,t,ans,sum,in[M],out[M],dis[M],cur[M];
struct edge
{
	int v,c,next;
}e[M*M];
void add(int u,int v,int c)
{
	e[++tot]=edge{v,c,f[u]},f[u]=tot;
	e[++tot]=edge{u,0,f[v]},f[v]=tot;
}
int bfs()
{
	queue<int> q;
	memset(dis,0,sizeof dis);
	q.push(s);dis[s]=1;
	while(!q.empty())
	{
		int u=q.front();q.pop();
		if(u==t) return 1;
		for(int i=f[u];i;i=e[i].next)
		{
			int v=e[i].v;
			if(!dis[v] && e[i].c>0)
			{
				dis[v]=dis[u]+1;
				q.push(v);
			}
		}
	}
	return 0;
}
int dfs(int u,int ept)
{
	if(u==t) return ept;
	int flow=0;
	for(int &i=cur[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(dis[v]==dis[u]+1 && e[i].c>0)
		{
			int tmp=dfs(v,min(e[i].c,ept));
			if(!tmp) continue;
			ept-=tmp;
			e[i].c-=tmp;
			e[i^1].c+=tmp;
			flow+=tmp;
			if(!ept) break;
		}
	}
	return flow;
}
int dinic()
{
	int res=0;
	while(bfs())
	{
		for(int i=1;i<=t;i++) cur[i]=f[i];
		res+=dfs(s,inf);
	}
	return res;
}
signed main()
{
	while(~scanf("%d %d",&n,&m))
	{
		tot=1;ans=sum=0;
		s1=n+m+1;t1=n+m+2;s=n+m+3;t=n+m+4;
		for(int i=1;i<=t;i++)
			in[i]=out[i]=f[i]=0;
		for(int i=1;i<=m;i++)
		{
			int x=read();
			out[i+n]+=x;in[t1]+=x;
			add(i+n,t1,inf-x);
		}
		for(int i=1;i<=n;i++)
		{
			int c=read(),d=read();
			add(s1,i,d);
			while(c--)
			{
				int t=read()+1,l=read(),r=read();
				out[i]+=l;in[t+n]+=l;
				add(i,t+n,r-l);
			}
		}
		for(int i=1;i<=t1;i++)
		{
			if(in[i]>out[i]) add(s,i,in[i]-out[i]),sum+=in[i]-out[i];
			else add(i,t,out[i]-in[i]);
		}
		add(t1,s1,inf);
		if(dinic()<sum)
		{
			puts("-1\n");
			continue;
		}
		ans=e[tot].c;//也就是返回流量的边流了多少
		e[tot].c=e[tot^1].c=0;//删去流量平衡边
		s=s1;t=t1;//更改源汇点
		ans+=dinic();
		printf("%d\n\n",ans);
	}
}
//注意清空 

有源汇上下界最小流

更有源汇上下界最大流很像啊,我们先求出有源汇上下界可行流。

类似地,我们跑 \(t->s\) 的最大流,然后减掉他就是答案,还是对环流的一个操作。

给出一道例题:清理雪道 ,要清理 \((x,y)\) 就建一条 \(x->y\) 的边流量下界为 \(1\) 就行了。

最小费用可行流

我准备结合这一道例题来讲:Incorrect Flow

首先原图的边可以看成 \((u,v)\) ,容量上界和下界都是 \(f\) ,当然这样不一定是合法的,为了不爆容量和流量平衡,我们还可以用增减 \(c,f\) 的操作,体现在图上可以这样建边(需要讨论 \(f,c\) 的关系):

对于 \(f\leq c\) 的情况:

  • 如果减少 \(f\) ,连 \((v,u)\) ,容量为 \(f\) ,费用为 \(1\)
  • 如果增大 \(f\) ,连 \((u,v)\) ,容量为 \(c-f\) ,费用为 \(1\)
  • 再增大 \(f\) 的话就要改 \(c\) 了,连 \((u,v)\) ,容量为 \(inf\) ,费用为 \(2\)

对于 \(f>c\) 的情况,先让他满足不爆容量,所以花 \(f-c\) 改一下 \(c\)

  • 如果减小 \(f\) ,连 \((v,u)\) ,那么 \(c\) 没有必要增加那么大了,容量为 \(f-c\) ,费用为 \(0\)
  • 再减小 \(f\) 的话 \(c\) 就相当于不改了,连 \((v,u)\) ,容量为 \(c\) ,费用为 \(1\)
  • 如果增大 \(f\) ,那么 \(f,c\) 都要再增大,连 \((u,v)\) ,容量为 \(inf\) ,费用为 \(2\)

最后连 \(t->s\) 的一条容量为 \(inf\) 的边来返还流量,这样就变成了一道最小费用可行流的问题了。

新建超级源点和超级汇点,考虑那些原来确定了流量的边,如果流入比流出多,那么 \(S->u\) ,容量为流入流出的差值,费用为 \(0\) 。如果流出比流入多,那么 \(u->T\) ,容量为流出流入的差值,费用为 \(0\) ,然后跑最小费用最大流。

#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
const int M = 105;
const int inf = 0x3f3f3f3f;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,S,T,tot,ans,d[M],f[M];
int flow[M],dis[M],lst[M],pre[M];
struct edge{int v,f,c,next;}e[M*M];
void add(int u,int v,int F,int c)
{
	e[++tot]=edge{v,F,c,f[u]},f[u]=tot;
	e[++tot]=edge{u,0,-c,f[v]},f[v]=tot;
}
int bfs()
{
	queue<int> q;
	for(int i=S;i<=T;i++)
		dis[i]=inf,flow[i]=pre[i]=lst[i]=0;
	dis[S]=0;flow[S]=inf;q.push(S);
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(int i=f[u];i;i=e[i].next)
		{
			int v=e[i].v,c=e[i].c;
			if(dis[v]>dis[u]+c && e[i].f>0)
			{
				dis[v]=dis[u]+c;
				flow[v]=min(flow[u],e[i].f);
				pre[v]=u;lst[v]=i;
				q.push(v);
			}
		}
	}
	return flow[T]>0;
}
signed main()
{
	n=read();m=read();
	S=0;T=n+1;tot=1;
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read(),c=read(),f=read();
		d[u]-=f;d[v]+=f;
		if(f<=c)
		{
			add(v,u,f,1);
			add(u,v,c-f,1);
			add(u,v,inf,2);
		}
		else
		{
			ans+=f-c;
			add(v,u,f-c,0);
			add(v,u,c,1);
			add(u,v,inf,2);
		}
	}
	for(int i=1;i<=n;i++)
	{
		if(d[i]>0) add(S,i,d[i],0);
		if(d[i]<0) add(i,T,-d[i],0);
	}
	add(n,1,inf,0);
	while(bfs())
	{
		ans+=flow[T]*dis[T];
		int nw=T;
		while(nw!=S)
		{
			e[lst[nw]].f-=flow[T];
			e[lst[nw]^1].f+=flow[T];
			nw=pre[nw];
		}
	}
	printf("%d\n",ans);
}

其他例题

矩阵

可以考虑二分答案 \(mid\) ,那么对于每一行都有这样的限制:\(\sum_{j=1}^ma_{i,j}-mid\leq \sum_{j=1}^m b_{i,j}\leq \sum_{j=1}^ma_{i,j}+mid\) ,对于列也有类似的柿子,我们把源点向每一行连边,容量就是上述的范围,汇点向每一列连边。

行和列之间两两连边,容量范围是 \([L,R]\) ,每一条边都表示一个位置数的选择,然后跑有源汇上下界可行流即可。

[TJOI2018]智力竞赛

挺简单的,有源汇上下界网络流,推一下我的洛谷博客

posted @ 2021-01-30 17:40  C202044zxy  阅读(327)  评论(0编辑  收藏  举报