在部分网络流问题中,会要求边的流量在一个区间 \([l,r]\) 之间,即新增了一个对流量下界的限制。
这种问题被称为「上下界网络流」。
如果你不知道什么是网络流,我建议你马上去读这里。
无源汇上下界可行流
求出一组流 \(f\) 使得每个点流量平衡。\(i\) 点是流量平衡的,当且仅当 \(\sum\limits_{j\neq i} f(j,i) = \sum \limits_{j\neq i} f(i,j)\),即流入流量等于流出流量。
对于第 \(i\) 条边 \((x_i,y_i,(l_i,r_i))\) 至少需要流 \(l_i\) 的流量,我们不妨将这部分流量流完,然后建立一个「残」网络,该网络中第 \(i\) 条边为 \((x_i,y_i,r_i-l_i)\)。
观察到,如果所有点流量平衡,那么我们就找到了一组可行流。反之,设每个点的净流入量(流入 - 流出)为 \(s_i\),那么:
- 如果 \(s_i=0\),那么该点流量平衡,我们不用处理这个点。
- 如果 \(s_i>0\),说明流入的流量过多,我们需要从该点流出 \(s_i\) 的流量到其它点。因此,我们需要一个在残网络上的虚拟源点 \(S'\),然后从 \(S'\) 流出 \(s_i\) 的流量到 \(i\)。
- 如果 \(s_i<0\),说明流出的流量过多,我们需要从其它点接受 \(s_i\) 的流量。因此,我们需要一个在残网络上的虚拟汇点 \(T'\),然后从该点流 \(s_i\) 的流量到 \(T'\)。
最后,在残网络上跑一遍 \(S'\) 到 \(T'\) 的最大流,满流(\(S'\) 流出的流量和 \(T'\) 流入的流量相等)则有一组可行流,否则不存在可行流。
正确性显然。
无源汇上下界最小费用可行流
把最大流改为费用流即可。
总费用即为费用流的费用加上流满每条边下界的费用。
有源汇上下界(最小费用)可行流
区别于无源汇的上下界网络流,有源汇上下界网络流对于源汇 \(S,T\),只需要满足 \(S\) 的净流出量等于 \(T\) 的净流入量。其它点仍然需要流量平衡。
一种做法是连一条 \((T,S,(0,+\infty))\) 的边,然后就变为了无源汇上下界网络流。\(S\) 到 \(T\) 的流量就是这条边流过的流量。
例题 1 [AHOI2014/JSOI2014]支线剧情
题意
给定 \(n\) 个点和 \(m\) 条边的带权有向图,保证从 \(1\) 开始可以走到全部 \(n\) 个点。每次可以选择一条从 \(1\) 开始的路径,给路径上的每一条边打上一个标记,代价为路径上的边权之和。询问给每条边都打上标记的最小代价。
\(1 \leq n \leq 300,1 \leq m \leq 5000\),边权 \(w_i\) 满足 \(1 \leq w_i \leq 300\)
题解
每条边至少经过一次等价于至少流过 \(1\) 的流量,因此边 \((u,v,w)\) 在网络流图中表示为 \((u,v,(1,+\infty),w)\)。路径可以在任何点结束,于是建立超级汇点 \(T\)(注意与虚拟汇点 \(T'\) 区分,它们不是同一个点),并对于每个点 \(i\) 连接边 \((i,T,(0,+\infty),0)\)。
然后就是一个源点为 \(1\) 汇点为 \(T\) 的有源汇上下界最小费用可行流。
# include <bits/stdc++.h>
const int N=100010,MAXN=1010,INF=0x3f3f3f3f;
struct Edge{
int to,next,v,w;
};
int fsum[MAXN],coss;
struct Dinic{
Edge edge[N];
int head[MAXN],sum;
bool vis[MAXN];
int dis[MAXN],s,t,cost;
Dinic(){
sum=1;
return;
}
inline void add(int x,int y,int v,int w){
edge[++sum]=(Edge){y,head[x],v,w},head[x]=sum;
return;
}
inline void addedge(int x,int y,int v,int w){
add(x,y,v,w),add(y,x,0,-w);
return;
}
inline bool spfa(void){
std::queue <int> q;
memset(dis,INF,sizeof(dis)),dis[s]=0;
q.push(s);
while(!q.empty()){
int i=q.front();
q.pop(),vis[i]=false;
for(int j=head[i];j;j=edge[j].next){
int to=edge[j].to;
if(edge[j].v&&dis[to]>dis[i]+edge[j].w){
dis[to]=dis[i]+edge[j].w;
if(!vis[to])
q.push(to),vis[to]=true;
}
}
}
return dis[t]<INF;
}
int dfs(int i,int flow){
if(i==t)
return flow;
vis[i]=true;
int maxsum=0;
for(int j=head[i];j;j=edge[j].next){
int to=edge[j].to;
if(vis[to]||!edge[j].v||dis[to]!=dis[i]+edge[j].w)
continue;
int res=dfs(to,std::min(edge[j].v,flow));
flow-=res,maxsum+=res,edge[j].v-=res,edge[j^1].v+=res,cost+=edge[j].w*res;
if(!flow)
break;
}
vis[i]=false;
if(!maxsum)
dis[i]=INF;
return maxsum;
}
inline void solve(void){
while(spfa()){
dfs(s,INF);
}
return;
}
}G;
int n;
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-')f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
int main(void){
n=read();
for(int i=1;i<=n;++i){
int d=read();
while(d--){
int to=read(),ti=read();
G.addedge(i,to,INF,ti);
++fsum[to],--fsum[i],coss+=ti;
}
}
// n+1 S'
// n+2 T'
// n+3 T
G.s=n+1,G.t=n+2;
for(int i=1;i<=n;++i)
G.addedge(i,n+3,INF,0);
G.addedge(n+3,1,INF,0); // 循环边
for(int i=1;i<=n;++i){
if(fsum[i]>0){
G.addedge(G.s,i,fsum[i],0);
}else if(fsum[i]<0){
G.addedge(i,G.t,-fsum[i],0);
}
}
G.solve();
printf("%d",coss+G.cost);
return 0;
}
有源汇上下界(最小费用)最大流
首先跑出一组可行流。如果它不存在,那么无解。
这个时候残量网络一定是流量平衡的。我们把 \(S',T'\) 和所有与它们相连的边删掉,再跑一遍 \(S\) 到 \(T\) 的(最小费用)最大流「榨干」剩下的流量即可。最后和可行流的流量相加。
当然实现的时候不删也是可以的,入度为 \(0\) 的 \(S'\) 和出度为 \(0\) 的 \(T'\) 显然也不会影响流量...
有源汇上下界(最小费用)最小流
考虑反向榨干,从 \(T\) 往 \(S\) 跑最大流,然后用可行流的流量减掉。