有上下界网络流 学习笔记
一、说在前头
前置知识:\(dinic\)
普通的网络流,我们只限制了每条路的流量,也就是它的上界,但有些特殊题目,还会恶心地限制一下每条道路流量的下界,今天我们要解决的就是这样的题目。
二、无源汇有上下界可行流
这个算法是所有上下界网络流算法的基础,因此我会重点阐述。首先解释一下标题:可行流是指一个网络流中存在的一条满足除源点汇点之外,所有点的流入量等于流出量(即流量守恒)的流。无源汇,就表明这个图中所有点都必须满足流入量等于流出量,我们只需要找到任意一条可行流即可。
首先,我们先让所有边流量强行达到下界,这样的一个流我们称为初始流,显然,初始流不一定满足流量守恒,所以我们需要通过适当在一些边上增加流量来使它满足流量守恒,新增加的流量合起来也是一条流,我们称为附加流。
附加流首先要满足不会让任意一条边流量超过上界,因此,我们将每条边的最大流量设为上界-下界,同时还要弥补初始流中的问题。对于点\(i\),令\(w_i\)表示初始流中\(i\)的流入量-流出量,我们对\(w_i\)分情况讨论:
- \(w_i=0\),初始流流量守恒,不需要对\(i\)点操作,让附加流满足流量守恒即可。
- \(w_i>0\),附加流在满足\(i\)点流量守恒之后,还要让它多流出\(w_i\)的流量,于是从附加流的源点\(S\)向\(i\)连一条流量为\(w_i\)的边。
- \(w_i<0\),附加流要让\(i\)点额外流入\(-w_i\)的流量,于是从\(i\)向附加流的汇点\(T\)连一条流量为\(-w_i\)的边。
为了满足流量守恒,额外连的这些边必须满流,于是在新图上跑最大流,只要流量\(=\sum{w_i}(w_i>0)=\sum{-w_i}(w_i<0)\)则说明有解,否则无解。注意到第二个等号一定满足,所以只用考虑第一个等号即可。
最后求可行流,则将附加流中每条原图中边的流量加上其下界即可。
模板:loj#115
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=410;
const int M=4e5+10;
const int inf=0x3f3f3f3f;
namespace flow{
struct node{
int v,f,nxt;
}e[M];
int tot;
int s,t,cnt=1,cur[N],first[N],vis[N],dis[N];
inline void add(int u,int v,int f){
e[++cnt].v=v;e[cnt].f=f;e[cnt].nxt=first[u];first[u]=cnt;
}
inline void Add(int u,int v,int f){
add(u,v,f);add(v,u,0);
}
inline bool bfs(){
fill(vis+1,vis+tot+1,0);
fill(dis+1,dis+tot+1,0);
queue<int> q;q.push(s);
dis[s]=0;vis[s]=1;
while(!q.empty()){
int u=q.front();q.pop();
for(int i=first[u];i;i=e[i].nxt){
int v=e[i].v;
if(e[i].f>0&&!vis[v]){
vis[v]=1;dis[v]=dis[u]+1;
q.push(v);
}
}
}
return vis[t];
}
inline int dfs(int u,int f){
if(u==t||!f) return f;
int used=0;
for(int &i=cur[u];i;i=e[i].nxt){
int v=e[i].v;
if(e[i].f>0&&dis[v]==dis[u]+1){
int fl=dfs(v,min(f,e[i].f));
used+=fl;f-=fl;
e[i].f-=fl;e[i^1].f+=fl;
if(!f) break;
}
}
if(f) dis[u]=inf;
return used;
}
inline int dinic(int S,int T){
int ans=0;s=S;t=T;
while(bfs()){
memcpy(cur,first,sizeof(first));
ans+=dfs(s,inf);
}
return ans;
}
inline void init(){
fill(first+1,first+tot+1,0);tot=0;
cnt=1;
}
}
using flow::tot;
using flow::Add;
using flow::cnt;
int n,m,s,t,S,T,w[N],pos[N],L[M];
int main(){
scanf("%d%d",&n,&m);
tot=n;S=++tot;T=++tot;
for(int i=1;i<=m;++i){
int u,v,l,r;
scanf("%d%d%d%d",&u,&v,&l,&r);L[i]=l;
Add(u,v,r-l);w[u]-=l;w[v]+=l;
}
int sum=0;
for(int i=1;i<=n;++i){
if(w[i]>0) sum+=w[i],Add(S,i,w[i]);
if(w[i]<0) Add(i,T,-w[i]);
}
if(flow::dinic(S,T)!=sum){
puts("NO");
return 0;
}
else{
puts("YES");
for(int i=1,j=3;i<=m;++i,j+=2)
printf("%d\n",L[i]+flow::e[j].f);
}
return 0;
}
三、有源汇有上下界可行流
增加了源点\(s\)和汇点\(t\)(注意与上文中的\(S\)与\(T\)区分),源点和汇点不需要满足流量守恒,源点可以任意流出,汇点可以任意流入。
于是我们直接增加一条从\(t\)连向\(s\)的流量上限为\(inf\),无下限的边,然后按照无源汇的做即可,同时这种情况下,可行流对应的原图流量,可以直接通过\(t\rightarrow s\)这条边的流量得到。
四、有源汇上下界最大流
在可行流的基础上,要求原图流量最大。
我们先根据上一个问题的方法,求解出一条可行流。此时我们一定满足了流量守恒,那么在附加流的残量网络上,我们任意跑出一条可行流,与之前的流合并都一定是一条原图上的可行流,于是直接在残量网络上跑\(s\rightarrow t\)的最大流,与之前的可行流合并即可。注意跑最大流的时候要把那条增加的\(t\rightarrow s\)的边流量给清零。
模板:loj#116
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=4e4+10;
const int M=4e5+10;
const int inf=0x3f3f3f3f;
namespace flow{
struct node{
int v,f,nxt;
}e[M];
int tot;
int s,t,cnt=1,cur[N],first[N],vis[N],dis[N];
inline void add(int u,int v,int f){
e[++cnt].v=v;e[cnt].f=f;e[cnt].nxt=first[u];first[u]=cnt;
}
inline void Add(int u,int v,int f){
add(u,v,f);add(v,u,0);
}
inline bool bfs(){
fill(vis+1,vis+tot+1,0);
fill(dis+1,dis+tot+1,0);
queue<int> q;q.push(s);
dis[s]=0;vis[s]=1;
while(!q.empty()){
int u=q.front();q.pop();
for(int i=first[u];i;i=e[i].nxt){
int v=e[i].v;
if(e[i].f>0&&!vis[v]){
vis[v]=1;dis[v]=dis[u]+1;
q.push(v);
}
}
}
return vis[t];
}
inline int dfs(int u,int f){
if(u==t||!f) return f;
int used=0;
for(int &i=cur[u];i;i=e[i].nxt){
int v=e[i].v;
if(e[i].f>0&&dis[v]==dis[u]+1){
int fl=dfs(v,min(f,e[i].f));
used+=fl;f-=fl;
e[i].f-=fl;e[i^1].f+=fl;
if(!f) break;
}
}
if(f) dis[u]=inf;
return used;
}
inline int dinic(int S,int T,int tp){
if(tp) e[2].f=e[3].f=0;
int ans=0;s=S;t=T;
while(bfs()){
memcpy(cur,first,sizeof(first));
ans+=dfs(s,inf);
}
return ans;
}
inline void init(){
fill(first+1,first+tot+1,0);tot=0;
cnt=1;
}
}
using flow::tot;
using flow::Add;
using flow::cnt;
int n,m,s,t,S,T,w[N],pos[N];
int main(){
scanf("%d%d%d%d",&n,&m,&s,&t);
tot=n;S=++tot;T=++tot;
Add(t,s,inf);
for(int i=1;i<=m;++i){
int u,v,l,r;
scanf("%d%d%d%d",&u,&v,&l,&r);
Add(u,v,r-l);w[u]-=l;w[v]+=l;
}
int sum=0;
for(int i=1;i<=n;++i){
if(w[i]>0) sum+=w[i],Add(S,i,w[i]);
if(w[i]<0) Add(i,T,-w[i]);
}
if(flow::dinic(S,T,0)!=sum){
puts("please go home to sleep");
return 0;
}
else printf("%d\n",flow::e[3].f+flow::dinic(s,t,1));
return 0;
}
五、有源汇上下界最小流
和上个问题没什么区别,先跑出一个可行流,然后在残量网络上跑\(t\rightarrow s\)的最大流,将这最大流的路径上所有道路的流量都撤回,依然得到一个可行流。因此答案就是可行流减去反向最大流。
模板:loj #117
#include<bits/stdc++.h>
using namespace std;
const int N=5e4+10;
const int M=4e5+10;
const int inf=0x3f3f3f3f;
namespace flow{
struct node{
int v,f,nxt;
}e[M];
int tot;
int s,t,cnt=1,cur[N],first[N],vis[N],dis[N];
inline void add(int u,int v,int f){
e[++cnt].v=v;e[cnt].f=f;e[cnt].nxt=first[u];first[u]=cnt;
}
inline void Add(int u,int v,int f){
add(u,v,f);add(v,u,0);
}
inline bool bfs(){
fill(vis+1,vis+tot+1,0);
fill(dis+1,dis+tot+1,0);
queue<int> q;q.push(s);
dis[s]=0;vis[s]=1;
while(!q.empty()){
int u=q.front();q.pop();
for(int i=first[u];i;i=e[i].nxt){
int v=e[i].v;
if(e[i].f>0&&!vis[v]){
vis[v]=1;dis[v]=dis[u]+1;
q.push(v);
}
}
}
return vis[t];
}
inline int dfs(int u,int f){
if(u==t||!f) return f;
int used=0;
for(int &i=cur[u];i;i=e[i].nxt){
int v=e[i].v;
if(e[i].f>0&&dis[v]==dis[u]+1){
int fl=dfs(v,min(f,e[i].f));
used+=fl;f-=fl;
e[i].f-=fl;e[i^1].f+=fl;
if(!f) break;
}
}
if(f) dis[u]=inf;
return used;
}
inline int dinic(int S,int T,int tp){
if(tp) e[2].f=e[3].f=0;
int ans=0;s=S;t=T;
while(bfs()){
memcpy(cur,first,sizeof(first));
ans+=dfs(s,inf);
}
return ans;
}
inline void init(){
fill(first+1,first+tot+1,0);tot=0;
cnt=1;
}
}
using flow::tot;
using flow::Add;
using flow::cnt;
int n,m,s,t,S,T,w[N],pos[N];
int main(){
scanf("%d%d%d%d",&n,&m,&s,&t);
tot=n;S=++tot;T=++tot;
Add(t,s,inf);
for(int i=1;i<=m;++i){
int u,v,l,r;
scanf("%d%d%d%d",&u,&v,&l,&r);
Add(u,v,r-l);w[u]-=l;w[v]+=l;
}
int sum=0;
for(int i=1;i<=n;++i){
if(w[i]>0) sum+=w[i],Add(S,i,w[i]);
if(w[i]<0) Add(i,T,-w[i]);
}
if(flow::dinic(S,T,0)!=sum){
puts("please go home to sleep");
return 0;
}
else printf("%d\n",flow::e[3].f-flow::dinic(t,s,1));
return 0;
}