图论-网络流⑧-有上下界的网络流
上一篇:图论-网络流⑦-费用流解题
参考文献:
https://www.cnblogs.com/DuskOB/p/11216861.html
https://www.cnblogs.com/-xiangyang/p/9773887.html
https://www.cnblogs.com/dreagonm/p/10803040.html
https://www.luogu.com.cn/problemnew/solution/P4043
大纲
- 什么是网络流
- 最大流(最小割)
- D i n i c Dinic Dinic (常用)
- E K EK EK
- S a p Sap Sap
- F o r d − F u l k e r s o n Ford-Fulkerson Ford−Fulkerson(不讲)
- H L P P HLPP HLPP (快)
- 最大流解题
- 费用流
- E K EK EK 费用流
- D i n i c Dinic Dinic 费用流
- z k w zkw zkw 费用流
费用流解题
有上下界的网络流 Start \color{#33cc00}\texttt{Start} Start
- 无源汇上下界可行流
- 有源汇上下界可行流
- 有源汇上下界最大流
- 有源汇上下界最小流
- 最大权闭合子图
- 有上下界的网络流解题 End \color{red}\texttt{End} End
上篇中讲完了费用流,这篇会讲一种特殊网络流:有上下界的网络流。
有上下界的网络流
普通的网络流(包括最大流和费用流)的边只有最大流量限制,默认最小流量限制为 0 0 0,但是你有没有想过最小流量限制不为 0 0 0 的网络最大流或网络费或可行流(满足限制的流)用流怎么做呢?
无源汇上下界可行流
网络流图中没有源点和汇点,每条边 i i i 的流量都被限制在区间 [ L i , R i ] [L_i,R_i] [Li,Ri]中,问该网络中达到可行流量时每条边的流量(找一个方案)。模板:传送门。
对于第 i i i 条边 ( u , v ) (u,v) (u,v),因为其流量区间大小为 R i − L i + 1 R_i-L_i+1 Ri−Li+1,正好等于普通网络流流量为 R i − L i R_i-L_i Ri−Li 时的区间大小,所以在网络流图中先连 ( u , v ) (u,v) (u,v),流量为 R i − L i R_i-L_i Ri−Li 以限制这条边的流量变化区间。
如果每个 L i = 0 L_i=0 Li=0,那么这样就已经对了。可是因为网络流需要满足每个节点的流入流量等于流出流量,所以记录 d [ x ] = ∑ i ∈ { x 的 入 边 } L i − ∑ i ∈ { x 的 出 边 } L i d[x]=\sum\limits _{i\in\{x的入边\}}L_i-\sum\limits_{i\in\{x的出边\}}L_i d[x]=i∈{x的入边}∑Li−i∈{x的出边}∑Li 。然后建立超级源点 S S S 和超级汇点 T T T。如果 d [ x ] > 0 d[x]>0 d[x]>0,就连 ( S , x ) (S,x) (S,x),流量为 d [ x ] d[x] d[x];如果 d [ x ] < 0 d[x]<0 d[x]<0,就连 ( x , T ) (x,T) (x,T),流量为 − d [ x ] -d[x] −d[x]。这样就起到了一个补流的作用, x x x 节点于是就这么流量守恒了。
同时,记录
s
u
m
=
∑
x
∈
图
节
点
&
&
d
[
x
]
>
0
d
[
x
]
sum=\sum\limits_{x\in图节点\&\&d[x]>0}d[x]
sum=x∈图节点&&d[x]>0∑d[x],
如果上面的网络流图跑出来的最大流
≠
s
u
m
\neq sum
=sum,说明不存在可行流。否则,原图(不包括补流边)每条边
i
i
i 在可行流中的流量可以是
f
w
[
x
⊕
1
]
+
L
i
fw[x\oplus 1]+L_i
fw[x⊕1]+Li,其中
f
w
[
x
⊕
1
]
fw[x\oplus 1]
fw[x⊕1] 表示这条边在最大流运行后实际流了的流量。
整理一下:
原图:
转化后图:
答案可行流:
代码:
#include <bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
template<int V,int M>
class Dinic{
public:int E,g[V],to[M],nex[M],fw[M];
void clear(){memset(g,0,sizeof g),E=1;}
Dinic(){clear();}
void add(int x,int y,int f){nex[++E]=g[x],to[E]=y,fw[E]=f,g[x]=E;}
void Add(int x,int y,int f){add(x,y,f),add(y,x,0);}
int dep[V],cur[V];bool vis[V];queue<int> q;
bool bfs(int s,int t,int p){
for(int i=1;i<=p;i++) vis[i]=0,cur[i]=g[i];
q.push(s),vis[s]=1,dep[s]=0;
while(q.size()){
int x=q.front();q.pop();
for(int i=g[x];i;i=nex[i])if(!vis[to[i]]&&fw[i])
q.push(to[i]),vis[to[i]]=1,dep[to[i]]=dep[x]+1;
}
return vis[t];
}
int dfs(int x,int t,int F){
if(x==t||!F) return F;
int f,flow=0;
for(int&i=cur[x];i;i=nex[i])
if(dep[to[i]]==dep[x]+1&&(f=dfs(to[i],t,min(F,fw[i])))>0)
{fw[i]-=f,fw[i^1]+=f,F-=f,flow+=f;if(!F) break;}
return flow;
}
int dinic(int s,int t,int p){
int res=0,f;
while(bfs(s,t,p)) while((f=dfs(s,t,inf))) res+=f;
return res;
}
};
int n,m,s,t,p,d[210],sum,low[40010];
vector<int> oedge;
Dinic<220,40010> net;
int main(){
scanf("%d%d",&n,&m);
p=t=n+2,s=t-1;
for(int i=1,x,y,l,r;i<=m;i++){
scanf("%d%d%d%d",&x,&y,&l,&r);
net.Add(x,y,r-l);
d[x]-=l,d[y]+=l;
low[net.E]=l;
oedge.push_back(net.E);
}
for(int i=1;i<=n;i++)
if(d[i]>0) net.Add(s,i,d[i]),sum+=d[i];
else if(d[i]<0) net.Add(i,t,-d[i]);
if(net.dinic(s,t,p)!=sum) return puts("NO"),0;
else {
puts("YES");
for(auto i:oedge)
printf("%d\n",net.fw[i]+low[i]);
}
return 0;
}
总结:比较偏理论吧。
有源汇上下界可行流
网络流图中有源点 s s s 和汇点 t t t,每条边 i i i 的流量都被限制在区间 [ L i , R i ] [Li,Ri] [Li,Ri] 中,问该网络中达到可行流量时每条边的流量以及 s s s 到 t t t 的流量。
如果连 ( t , s ) (t,s) (t,s) 流量为 ∞ \infty ∞,就转化成了无源汇上下界可行流问题,然后增加超级源汇补流边,解法同上,代码同上。
有源汇上下界最大流
网络流图中有源点 s s s 和汇点 t t t,每条边 i i i 的流量都被限制在区间 [ L i , R i ] [Li,Ri] [Li,Ri] 中,问该网络中达到可行最大流量时每条边的流量以及 s s s 到 t t t 的流量。
先跑一遍有源汇上下界可行流。如果可行,就用残余跑一次最大流,然后两个结果相加就是有源汇上下界最大流。解法同上,代码同上。
真的不是在敷衍你,就是这样的!
有源汇上下界最小流
比较复杂。先建超级源点 S S S,超级汇点 T T T,然后按照规则连补流边,对 S → T S\to T S→T 跑一次最大流。然后对原图连一条 ( t , s ) (t,s) (t,s) 的流量为 ∞ \infty ∞ 的边,然后再在残余网络上跑一次 S → T S\to T S→T 的最大流。然后答案就是最后加的 ( t , s ) (t,s) (t,s) 边实际流的流量。 模板:传送门。
原理就是先跑个可行流然后不停缩减流量。
为了更好理解,放个代码:
#include <bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
template<int V,int M>
class Dinic{
public:int E,g[V],to[M],nex[M],fw[M];
void clear(){memset(g,0,sizeof g),E=1;}
Dinic(){clear();}
void add(int x,int y,int f){nex[++E]=g[x],to[E]=y,fw[E]=f,g[x]=E;}
void Add(int x,int y,int f){add(x,y,f),add(y,x,0);}
int dep[V],cur[V];bool vis[V];queue<int> q;
bool bfs(int s,int t,int p){
for(int i=1;i<=p;i++) vis[i]=0,cur[i]=g[i];
q.push(s),vis[s]=1,dep[s]=0;
while(q.size()){
int x=q.front();q.pop();
for(int i=g[x];i;i=nex[i])if(!vis[to[i]]&&fw[i])
q.push(to[i]),vis[to[i]]=1,dep[to[i]]=dep[x]+1;
}
return vis[t];
}
int dfs(int x,int t,int F){
if(x==t||!F) return F;
int f,flow=0;
for(int&i=cur[x];i;i=nex[i])
if(dep[to[i]]==dep[x]+1&&(f=dfs(to[i],t,min(F,fw[i])))>0)
{fw[i]-=f,fw[i^1]+=f,F-=f,flow+=f;if(!F) break;}
return flow;
}
int dinic(int s,int t,int p){
int res=0,f;
while(bfs(s,t,p)) while((f=dfs(s,t,inf))) res+=f;
return res;
}
};
const int N=6e4+10;
const int M=5e5+10;
int n,m,s,t,S,T,p,d[N],sum,ans;
Dinic<N,M> net;
int main(){
scanf("%d%d%d%d",&n,&m,&s,&t);
p=T=n+2,S=T-1;
for(int i=1,x,y,l,r;i<=m;i++){
scanf("%d%d%d%d",&x,&y,&l,&r);
net.Add(x,y,r-l);
d[x]-=l,d[y]+=l;
}
for(int i=1;i<=n;i++)
if(d[i]>0) net.Add(S,i,d[i]),sum+=d[i];
else if(d[i]<0) net.Add(i,T,-d[i]);
ans+=net.dinic(S,T,p); //三行核心代码
net.Add(t,s,inf); //三行核心代码
ans+=net.dinic(S,T,p); //三行核心代码
if(ans!=sum) puts("please go home to sleep");
else printf("%d\n",net.fw[net.E]);
return 0;
}
最大权闭合子图
闭合图是在一个图中,选取一些点构成点集,若集合中任意点连接的出边所通往的点也在该点集中,则这个点集以及所有这些边构成闭合图。最大权闭合子图即点权之和最大的闭合图。
要求一个图的最大权闭合子图,先建立源点 s s s 和汇点 t t t。然后连 ( s , (s, (s, 正权点 ) ) ),流量为点的权值;连 ( ( (负权点 , t ) ,t) ,t) ,流量为点权的相反数。点与点之间按照边原来的方向连流量为 ∞ \infty ∞ 的边。
然后该图的最大权闭合子图就为 ( ( (正权点权值之和 − - −网络流图最大流 ) ) )。
有上下界的网络流解题
该算法其实题目较少,属于冷门算法。但蒟蒻找了很久还是找到了一道好例题。
[AHOI2014/JSOI2014]支线剧情
看了题目,你会谔谔道:这是有上下界的费用流!你是对的,如果你掌握了有上下界的最大流,也就掌握了有上下界的费用流。
对于像有上下界的网络流这样的偏理论性算法,应该做到触类旁通,这题虽然是“有上下界的费用流”,但和有上下界的最大流本质是一样的。
很明显,为了先构建一个有上下界的网络最大流模型,要增加汇点 t ′ t' t′(因为 t t t 是题面中的变量,所以不能用),使所有不是树根节点的节点可以连到 t ′ t' t′,流量范围 [ 0 , ∞ ] [0,\infty] [0,∞],费用 0 0 0,以确保看完剧情。然后对于每条树上边,看作流量下限为 1 1 1 上限为 ∞ \infty ∞ ,费用为 t i , j t_{i,j} ti,j 的费用流边。
然后按照解决有有源汇有上下界的网络可行流的方法,跑个最小费用可行流就好了。代码如下:
#include <bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
template<int V,int M>
class zkwmcmf{
public:int E,g[V],to[M],nex[M],fw[M],ct[M],fans,cans;
void clear(){memset(g,0,sizeof g),E=1,fans=cans=0;}
zkwmcmf(){clear();}
void add(int x,int y,int f,int c){
nex[++E]=g[x],to[E]=y,fw[E]=f,ct[E]=c,g[x]=E;
}
void Add(int x,int y,int f,int c){add(x,y,f,c),add(y,x,0,-c);}
int dep[V];bool vis[V];deque<int> q;
bool spfa(int s,int t,int p){
for(int i=1;i<=p;i++) vis[i]=0,dep[i]=inf;
q.push_back(t),dep[t]=0,vis[t]=1;
while(q.size()){
int x=q.front();q.pop_front(),vis[x]=0;
for(int i=g[x];i;i=nex[i])
if(fw[i^1]&&dep[to[i]]>dep[x]-ct[i]){
dep[to[i]]=dep[x]-ct[i];
if(!vis[to[i]]){
vis[to[i]]=1;
if(q.size()&&dep[to[i]]<dep[q.front()])
q.push_front(to[i]);
else q.push_back(to[i]);
}
}
}
return dep[s]<inf;
}
int dfs(int x,int t,int F){
vis[x]=1;
if(x==t||!F) return F;
int f,flow=0;
for(int i=g[x];i;i=nex[i])if(fw[i]&&!vis[to[i]]&&
dep[to[i]]==dep[x]-ct[i]&&(f=dfs(to[i],t,min(F,fw[i])))>0)
{cans+=ct[i]*f,fw[i]-=f,fw[i^1]+=f,F-=f,flow+=f;if(!F)break;}
return flow;
}
void mcmf(int s,int t,int p){
while(spfa(s,t,p)){
vis[t]=1;
while(vis[t]){
memset(vis,0,sizeof vis);
fans+=dfs(s,t,inf);
}
}
}
};
int n,ans,d[510],s,t,p;
zkwmcmf<510,100010> net;
int main(){
scanf("%d",&n);
p=t=n+3,s=t-1;
for(int i=1,x,y,z;i<=n;i++){
scanf("%d",&x);
for(int j=1;j<=x;j++){
scanf("%d%d",&y,&z);
d[i]-=1,d[y]+=1,ans+=z;
net.Add(i,y,inf,z);
}
}
for(int i=2;i<=n;i++) net.Add(i,n+1,inf,0);
for(int i=1;i<=n;i++){
if(d[i]>0) net.Add(s,i,d[i],0);
if(d[i]<0) net.Add(i,t,-d[i],0);
}
net.Add(n+1,1,inf,0);
//代码其实就是一模一样的,费用流,最大流,到头来还是同个东西
net.mcmf(s,t,p);
printf("%d\n",net.cans+ans);
return 0;
}
总结:费用流,最大流,到头来还是同个东西。
然后这次网络流就讲到这里了,没有网络流⑨了。如果喜欢蒟蒻写的博文,就点个赞。蒟蒻后期还会写最小割的高级知识 G H T GHT GHT(最小割树)的,感谢你的支持。
祝大家学习愉快!