再谈网络流/二分图

1.上下界网络流

无源汇上下界可行流

每条边不仅有容量上界,还有容量下界,必须至少有若干流量流过。
且原图没有源点和汇点。
\(f_0\) 为“每条边都恰好流了下界”的方案。
\(f_0\) 可能不满足流量守恒。

我们记 \(p_u=\sum_v f_0(u\rightarrow v)\)
\(p_u>0\) ,表示这个点凭空产生了 \(p_u\) 的流量,
\(p_u<0\) ,表示这个点要吃掉 \(-p_u\) 的流量。

我们新建一张图 \(G*\),其中每条边的容量均减去下界。
再建立超级源点 \(S\),和超级汇点 \(T\)
对于凭空产生流量的点,用 \(S\) 连向它,容量 \(p_u\).
对于吃掉流量的点,连向 \(T\),容量 \(-p_u\).
求最大流,若新建的边能满流,即满足流量守恒,则有解。

\(G∗\)\(S\rightarrow T\) 最大流,即可得到 \(G\) 中一种可行的 \(f′\) 方案,再加上 \(f_0\) 就得到 \(f\)

有源汇上下界可行流

给图中加入两个点 \(s,t\) 可以不满足流量守恒限制。
我们可以建立一条从 \(t\) 连向 \(s\) 的边,容量无限,称为“电池边”。
这样就满足了流量守恒限制。
然后问题转化为无源汇上下界可行流。

有源汇上下界最大流

即求出在所有合法的流中,电池边流量最大的。
先求出有源汇上下界可行流,然后去掉电池边和超级源汇,直接在 \(s\rightarrow t\) 之间跑一次最大流进行调整。
注意这里要在 \(G*\) 中去跑。

code
#include <bits/stdc++.h>
using namespace std;
const int N=2e3+5;
const int M=2e5+5;
struct flow {
	int cnt=1,head[N],nxt[M<<1],ver[M<<1],limit[M<<1];
	void clear() {
		memset(head,0,sizeof head);
		cnt=1;
	}
	void add(int u,int v,int w) {
		nxt[++cnt]=head[u],head[u]=cnt,ver[cnt]=v,limit[cnt]=w;
		nxt[++cnt]=head[v],head[v]=cnt,ver[cnt]=u,limit[cnt]=0;
	}
	int T,dis[N],cur[N];
	int dfs(int id,int res) {
		if(id==T) return res;
		int flow=0;
		for(int i=cur[id]; i&&res; i=nxt[i]) {
			cur[id]=i;
			int c=min(res,(int)limit[i]),it=ver[i];
			if(dis[id]+1==dis[it]&&c) {
				int k=dfs(it,c);
				flow+=k,res-=k,limit[i]-=k,limit[i^1]+=k;
			}
		}
		if(!flow) dis[id]=-1;
		return flow;
	}
	int maxflow(int s,int t) {
		T=t;
		int flow=0;
		while(1) {
			queue<int> q;
			memcpy(cur,head,sizeof(head));
			memset(dis,-1,sizeof(dis));
			q.push(s),dis[s]=0;
			while(!q.empty()) {
				int t=q.front();
				q.pop();
				for(int i=head[t]; i; i=nxt[i])
					if(dis[ver[i]]==-1&&limit[i])
						dis[ver[i]]=dis[t]+1,q.push(ver[i]);
			}
			if(dis[t]==-1) return flow;
			flow+=dfs(s,1e9);
		}
	}
};
struct bound {
	int cnt,head[N],nxt[M],ver[M],low[M],high[M];
	flow g;
	void clear() {
		g.clear();
		memset(head,0,sizeof head),cnt=0;
	}
	void add(int u,int v,int x,int y) {
		nxt[++cnt]=head[u],head[u]=cnt,ver[cnt]=v,low[cnt]=x,high[cnt]=y;
	}
	int maxflow(int n,int s,int t,int ss,int tt) {
		int w[N];
		memset(w,0,sizeof(w));
		for(int i=0; i<=n; i++)
			for(int j=head[i]; j; j=nxt[j]) {
				w[i]-=low[j],w[ver[j]]+=low[j];
				high[j]-=low[j];
				g.add(i,ver[j],high[j]);
			}
		int sum=0;
		for(int i=0; i<=n; i++)
			if(w[i]>0) g.add(ss,i,w[i]),sum+=w[i];
			else g.add(i,tt,-w[i]);
		g.add(t,s,1e9);
		int res=g.maxflow(ss,tt);
		if(res!=sum) return -1;
		res=g.limit[g.head[s]];
		g.head[s]=g.nxt[g.head[s]];
		g.head[t]=g.nxt[g.head[t]];
		return res+g.maxflow(s,t);
	}
} g;
int n,m;
int main() {
	ios::sync_with_stdio(0);
	while(cin>>n>>m) {
		int s=0,t=n+m+1;
		int ss=t+1,tt=t+2;
		g.clear();
		for(int i=1,w; i<=m; i++) {
			cin>>w;
			g.add(n+i,t,w,1e9);
		}
		for(int i=1,c,d,x,l,r; i<=n; i++) {
			cin>>c>>d;
			g.add(s,i,0,d);
			while(c--) {
				cin>>x>>l>>r;
				g.add(i,n+x+1,l,r);
			}
		}
		cout<<g.maxflow(t,s,t,ss,tt)<<endl<<endl;
	}
	return 0;
}

2.Hall 定理

在二分图中有完美匹配,当且仅当对于任意点集 \(S⊆L\),都有 \(|S|\le N(S)\).
其中 \(N(S)\) 为邻域,表示 \(S\) 连到 \(R\) 中的点集.
进而得出,任意的二分图最大匹配为 \(\min_S |L|-|S|+N(S)\).

证明:建立网络流模型,最大流最小割定理得证。

3.几个题

P4126 [AHOI2009] 最小割

问对于每条边,回答其是否出现在任何一种最小割中(可行边)。
回答其是否出现在任意最小割中(必须边)。
必须边属于可行边。

先跑最大流。
可行边在任意的最大流方案中必须满流,则排除不满流的边。
考虑能否将流方案调整(加一个无源汇流)使得原来满流的边不满流。
若残量网格中其属于一个环,则可以使其不满流。

考虑将残量网格缩强连通分量。
有结论:
可行边:满流,两个端点不同属一个强连通分量之中。
必须边:是可行边,且链接 \(s\)\(t\) 的强连通分量。

ARC106E Medals

\(n\le 18\) 个员工,从今天开始,第 \(i\) 位员工重复“干 \(Ai\) 天活,休息 \(Ai\) 天”。
每一天,你可以选择的当天上班的员工之一,颁发一枚奖章。
给每位员工颁发至少 \(k\) 枚奖牌至少需要多少天?

考虑二分 \(mid\) 天。
此时转化为一个二分图完美匹配的问题,左边是员工,右边是 \(mid\) 天。
考虑使用 Hall 定理。
我们先预处理出每天有什么人来,得出恰好有某个集合人来的天数。
高维前缀和处理一下,求出某个集合的超集和,就是这个集合在二分图中的邻域。

posted @ 2023-08-01 13:45  s1monG  阅读(39)  评论(0)    收藏  举报