有上下界网络流学习笔记
本文作者是跟着这篇 有上下界的网络流学习笔记 - liu_runda 学的,如有雷同,并非巧合。
备注:在费用流中需要强制钦定某条边流满时,我们可以强制其费用为正或负 \(\infty\) ,这样就一定会流满了。
无源汇有上下界可行流
定义:一个满足每条边的流量符合再上下界之间并且每一个点的流出量等于流入量。
考虑如何求解可行流。我们的大体思路是先找到一个不满足流量守恒但满足上下界的流,然后将其调整为满足流量守恒的流。
首先,由于可行流一定满足每条边的流量都大于下界,所以我们不妨先令每条边的流量都为下界,这样的话我们就已然得到了一个满足上下界但是不满足流量守恒的初始流了。我们接下来要做的,便是加大其中一些边的,使其满足流量守恒。
我们此时考虑再建出一个补充流,使得该流与我们上面初始的流合并之后能够满足流量守恒。那么显然的,该流中没点的流出量和流入量的差,正好与我们的初始流相反。
考虑实际上我们可以通过建一个网络流模型来求出这个补充流,具体的我们另设一对虚拟源汇点 \(\mathbb S,\mathbb T\) ,将 \(\mathbb S\) 与所有需要补充流入量的点相连,流量设为需要补充的流入量;将所有需要补充流出量的点与 \(\mathbb T\) 相连,设流量为需要补充的流出量。同时剩下的原图上的边都设为上界减去下界。在这张图上跑最大流必然就可以得到满足上述边都跑满的流,也就是我们需要找到的补充流。
最后两个图合并即可。
例题
有源汇有上下界可行流
由汇点向源点连一条流量为 \(\infty\) 且没有上下界的边即可,这样我们就转化为了上面的情况。
例题
P5192 Zoj3229 Shoot the Bullet
#include<bits/stdc++.h>
using namespace std;
const int N=370,M=1e3+5;
const int INF=1e9+7;
int n,m,c[M],d[N];
int day[N],girl[M];
struct Network_Flow{
int from,to,tot;Network_Flow(){e_size=1;}
struct Edge{int nxt,to,flow;}e[N*M*2+N*2+M*2];int fir[N+M],e_size;
void add(int u,int v,int w){e[++e_size]=(Edge){fir[u],v,w},fir[u]=e_size;}
int cur[N+M],dis[N+M];bool vis[N+M];queue<int> q;
void clear(){tot=0,e_size=1,memset(fir,0,sizeof(fir));}
bool bfs(){
for(int i=1;i<=tot;++i) cur[i]=fir[i],dis[i]=INF;
dis[from]=0,q.push(from);
while(!q.empty()){
int u=q.front();q.pop();
for(int i=fir[u];i;i=e[i].nxt){
int v=e[i].to;if(!e[i].flow||dis[v]<=dis[u]+1) continue;
dis[v]=dis[u]+1,q.push(v);
}
}
return dis[to]!=INF;
}
int dfs(int u,int flow){
if(u==to) return flow;
int res=0;vis[u]=true;
for(int i=cur[u];i&&flow;i=e[i].nxt){
int v=e[i].to;cur[u]=i;
if(vis[v]||!e[i].flow||dis[v]!=dis[u]+1) continue;
int tmp=dfs(v,min(flow,e[i].flow));
e[i].flow-=tmp,e[i^1].flow+=tmp,flow-=tmp,res+=tmp;
}
return vis[u]=false,res;
}
int dinic(){
int res=0;
while(bfs()) res+=dfs(from,INF);
return res;
}
}g,h;
struct Limit_Network_Flow{
int from,to,tot,flow[N+M];
struct Edge{int nxt,from,to,L,R,flow;}e[N*M+N+M];int fir[N+M],e_size;
void add(int u,int v,int w1,int w2){e[++e_size]=(Edge){fir[u],u,v,w1,w2,0},fir[u]=e_size;}
void clear(){tot=e_size=0,memset(fir,0,sizeof(fir));}
int work(Network_Flow &g){
g.tot=tot,g.from=++g.tot,g.to=++g.tot;
add(to,from,0,INF),memset(flow,0,sizeof(flow));
for(int i=1;i<=e_size;++i){
e[i].flow=e[i].L,flow[e[i].to]+=e[i].L,flow[e[i].from]-=e[i].L;
g.add(e[i].from,e[i].to,e[i].R-e[i].L),g.add(e[i].to,e[i].from,0);
}
for(int i=1;i<=tot;++i){
if(flow[i]>0) g.add(g.from,i,flow[i]),g.add(i,g.from,0);
else g.add(i,g.to,-flow[i]),g.add(g.to,i,0);
};
g.dinic();
for(int i=1;i<=e_size;++i) e[i].flow+=g.e[i<<1|1].flow;
for(int i=e_size+1;i<=e_size+tot;++i) if(g.e[i<<1].flow) return -1;
return e[e_size--].flow;
}
int WORK(Network_Flow &g){
g.from=from,g.to=to,g.tot=tot;
for(int i=1;i<=e_size;++i){
g.add(e[i].from,e[i].to,e[i].R-e[i].flow);
g.add(e[i].to,e[i].from,0);
}
return g.dinic();
}
}f;
int solve(){
f.clear(),g.clear(),h.clear();
f.from=++f.tot,f.to=++f.tot;
for(int i=1;i<=n;++i) day[i]=++f.tot;
for(int i=1;i<=m;++i) girl[i]=++f.tot;
for(int i=1;i<=m;++i){
scanf("%d",&c[i]);
f.add(girl[i],f.to,c[i],INF);
}
for(int i=1,k;i<=n;++i){
scanf("%d%d",&k,&d[i]);
f.add(f.from,day[i],0,d[i]);
for(int j=1;j<=k;++j){
int t,l,r;scanf("%d%d%d",&t,&l,&r);
f.add(day[i],girl[t+1],l,r);
}
}
int res=0;
res+=f.work(g);if(res==-1) return printf("-1\n\n"),0;
res+=f.WORK(h);return printf("%d\n\n",res),0;
}
int main(){
while(~scanf("%d%d",&n,&m)) solve();
}
有源汇有上下界最大流
求出一个可行流,将上界减去对应流量,然后跑最大流即可。最后将两张图合并。
有源汇有上下界最小流
求出一个可行流,将当前流量减去下界,然后跑最大流即可。最后将两张图合并。
有源汇有上下界费用流
一个 \(\text{naive}\) 的想法,将 \(\text{bfs}\) 换成 \(\text{spfa}\) 就行了?(有待思考)
咕咕咕。