上下界网络流

上下界网络流

给你一张网络 , 对于一条边有两个限制 \(low(u,v),high(u,v)\),要求实际通过的流量为 \(low \le f(u,v) \le high\). 在此基础上满足流量守恒(流出的流量 = 流入的流量)

概述

无源汇上下界网络流

移项可得 \(low(u,v) \le f(u,v) \le high(u,v) \Rightarrow 0 \le f(u,v) \le high(u,v) - low(u,v)\) , 每条边都变成 \(high - low\) 的形式,表示下界跑满。容易发现,按照这种做法,对于每一个节点 \(pos\),一共有 \(in = \sum low(u,pos)\) 的流量流入;一共有 \(out = \sum low(pos,u)\) 的流量流出。

\(out\)\(in\) 的差值指示这这个点能不能遵循流量守恒。如果入流量多了,我们从虚拟源点 \(S\) 里面连一条容量为 \(in - out\) 的边到 \(pos\) , 否则连到 \(T\) 去。

跑一边最大流,如果满足流量守恒,也就是从 \(S\) 出来的权值 = \(T\) 接收到的权值,那么残量网络减少的值 + 下界就是一组解。

有源汇上下界网络流

这个简单,连一条边 \(T\rightarrow S\),边权为 \([0,\inf]\) , 这样就转换成无源汇问题。

理解:\(S,T\) 这两个点非常特殊,可以不用满足流量守恒。

有源汇上下界最大流

先跑一次无源汇上下界网络流构造出可行流,然后在残量网络上跑一次从 \(S\)\(T\) 的最大流,把能推的流量推到 \(T\) 那里去。

有源汇上下界最小流

和最大流差不多,只不过是从 \(T\)\(S\) 跑一次最大流把能送回来的流量送回来。

例题

模板题:P5192 Zoj3229 Shoot the Bullet|东方文花帖|【模板】有源汇上下界最大流 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

P4843 清理雪道

这个题乍一看是最小链覆盖问题,但是传递闭包之后的边规模达到了惊人的 \(O(n^3)\) , 无法进行二分图匹配。

有一个条件:每条边必须经过一次,也就是下界。最小流即可解决。

堆一个模板代码:

//{{{ template
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <queue>
#include <set>
#include <vector>
#include <map>
#include <cassert>
#include <deque>
#include <cmath>
using namespace std;
#define int long long
inline int gi(){
	char tmp=getchar();int ans=0,flag=1;
	while(!isdigit(tmp)){
		if(tmp == '-') flag = -1;
		tmp = getchar();
	}
	while(isdigit(tmp)){
		ans = ans * 10 + tmp - '0';
		tmp = getchar();
	}
	return ans * flag;
}
inline void in(int &x){x=gi();}
inline void in2(int &x,int &y){in(x),in(y);}
inline void in3(int &x,int &y,int &z){in(x),in(y),in(z);}
inline void in4(int &x,int &y,int &z,int &a){in3(x,y,z),in(a);}
inline void smax(int &x,int y){x=max(x,y);}
inline void smin(int &x,int y){x=min(x,y);}
#define Mem(arr,v) memset(arr,v,sizeof arr)
#define Copy(arr,goal) memcpy(arr,goal,sizeof goal)
#define For(i,a,b) for(int i=(int)(a);i<=(int)(b);++i)
// }}}
const int N = 42 * 42 * 30;
struct Edg{
	int v,flow,nxt;
}Edge[2002000];
int Head[N],Cur[N];
int Bal[N],G[N];
int cnt = 1;
void Add(int u,int v,int lower,int upper){
	Bal[v] += lower; Bal[u] -= lower;
	// printf("%d %d %d~%d\n",u,v,lower,upper);
	Edge[++cnt].v = v;Edge[cnt].nxt = Head[u]; Edge[cnt].flow = upper - lower; Head[u] = cnt;
	Edge[++cnt].v = u;Edge[cnt].nxt = Head[v]; Edge[cnt].flow = 0; Head[v] = cnt;
}
int Dep[N];
int S = N-2 , T = N - 1;

int Bfs(){
	Mem(Dep,0);Copy(Cur,Head);
	queue<int> Q;
	Q.push(S);Dep[S] = 1;
	while(!Q.empty()){
		int pos = Q.front(); Q.pop();
		for(int i = Head[pos]; i; i = Edge[i].nxt){
			int arr = Edge[i].v;
			// if(arr == T){
				// printf("%lld ",pos);
			// }
			if(!Dep[arr] && Edge[i].flow){
				Dep[arr] = Dep[pos] + 1;
				Q.push(arr);
			}
		}
	}
	return Dep[T];
}
int Dfs(int pos,int flow){
	if(pos == T){
		return flow;
	}
	int used = 0;
	for(int i = Cur[pos];i;i = Edge[i].nxt){
		Cur[pos] = i;
		int arr = Edge[i].v;
		if(Dep[arr] == Dep[pos] + 1 && Edge[i].flow){
			int tmp = Dfs(arr,min(flow - used , Edge[i].flow));
			if(tmp){
				Edge[i].flow -= tmp ; Edge[i ^ 1].flow += tmp;
				used += tmp;
			}
			if(used == flow) return used;
		}
	}
	return used;
}
int Dinic(){
	int ans = 0;
	while(Bfs())
		ans += Dfs(S,1e9 + 7);
	return ans;
}
int n,m;
signed main()
{
#ifdef NICEGUODONG
	freopen("data.in","r",stdin);
#endif
	int n = gi();
	int fs = 0 ,ft = n + 1;
	int inf = 1e8;
	For(i,1,n){
		int m = gi();
		For(j,1,m){
			int a = gi();
			Add(i,a,1,1e8);
		}
	}
	For(i,1,n) Add(fs,i,0,inf);
	For(i,1,n) Add(i,ft,0,inf);
	For(i,0,n+1){
		if(Bal[i] > 0)
			Add(S,i,0,Bal[i]);
		else if (Bal[i] < 0){
			Add(i,T,0,-Bal[i]);
			// printf("%d %d %d\n",i,T,Bal[i]);
		}
	}
	Add(ft,fs,0,inf);
	Dinic();
	int ans = Edge[cnt].flow;
	Edge[cnt].flow = Edge[cnt-1].flow = 0;
	T = fs, S = ft;
	printf("%lld\n",ans - Dinic());
	return 0;
}
posted @ 2021-08-02 22:51  guodongLovesOi  阅读(53)  评论(0编辑  收藏  举报