再谈网络流/二分图
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 定理。
我们先预处理出每天有什么人来,得出恰好有某个集合人来的天数。
高维前缀和处理一下,求出某个集合的超集和,就是这个集合在二分图中的邻域。