一、说在前头
前置知识:(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
",L[i]+flow::e[j].f);
}
return 0;
}
三、有源汇有上下界可行流
增加了源点(s)和汇点(t)(注意与上文中的(S)与(T)区分),源点和汇点不需要满足流量守恒,源点可以任意流出,汇点可以任意流入。
于是我们直接增加一条从(t)连向(s)的流量上限为(inf),无下限的边,然后按照无源汇的做即可,同时这种情况下,可行流对应的原图流量,可以直接通过(t ightarrow s)这条边的流量得到。
四、有源汇上下界最大流
在可行流的基础上,要求原图流量最大。
我们先根据上一个问题的方法,求解出一条可行流。此时我们一定满足了流量守恒,那么在附加流的残量网络上,我们任意跑出一条可行流,与之前的流合并都一定是一条原图上的可行流,于是直接在残量网络上跑(s ightarrow t)的最大流,与之前的可行流合并即可。注意跑最大流的时候要把那条增加的(t ightarrow 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
",flow::e[3].f+flow::dinic(s,t,1));
return 0;
}#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(){
while(scanf("%d%d",&n,&m)!=EOF){
fill(w+1,w+tot+1,0);
flow::init();
s=++tot;t=++tot;S=++tot;T=++tot;
Add(t,s,inf);
for(int i=1,g;i<=m;++i){
scanf("%d",&g);
tot++;
Add(tot,t,inf);
w[tot]-=g;w[t]+=g;
}
for(int i=1,c,d;i<=n;++i){
scanf("%d%d",&c,&d);
tot++;
Add(s,tot,d);
for(int j=1,t,l,r;j<=c;++j){
scanf("%d%d%d",&t,&l,&r);t++;
Add(tot,t+4,r-l);
w[t+4]+=l;w[tot]-=l;
}
}
int sum=0;
for(int i=1;i<=tot;++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("-1
");
continue;
}
printf("%d
",flow::e[3].f+flow::dinic(s,t,1));
}
return 0;
}
五、有源汇上下界最小流
和上个问题没什么区别,先跑出一个可行流,然后在残量网络上跑(t ightarrow 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
",flow::e[3].f-flow::dinic(t,s,1));
return 0;
}