本文为博主原创文章,转载请标明出处。
原文链接
本文概述
1. 问题模型
2. 前言
3. 无源汇可行流
4. 有源汇可行流
5. 有源汇最大流
6. 有源汇最小流
7. 模板题链接
8. 模板
9. 鸣谢
问题模型
$n$ 个点 , $m$ 条边。每一条边有流量下界和上界,定义边 $(x,y,L,U)$ 表示一条从 $x$ 到 $y$ 的边,流量的下界和上界分别为 $L$ 和 $U$ ,求……
前言
解决这类网络流问题的本质就是通过特殊构图将上下界网络流问题变成普通的最大流问题。由于这几种构造的正确性都很容易感性理解,所以这里不证明了。
无源汇可行流
问题
求一个使得所有边的满足上下界限制且每一个点的流量都平衡的方案。
构造方案
定义 “边 $(x,y,cap)$" 表示一条从 $x$ 到 $y$ 且容量为 $cap$ 的边。
首先,对于每一条边 $(x,y,L,U)$ ,我们在图中建边 $(x,y,U-L)$ 。
这样显然已经导致了途中流量的不平衡,记点 $x$ 当前入流量与出流量之差为 $in[x]$ 。我们新建超级源 $S$ 和超级汇 $T$ ,对于当前点 $i$,如果流量不平衡,那么我们分两种情况讨论:
1. 入度大于出度,即 $in[i]>0$ ,那么我们在图中建立边 $(S,i,in[i])$ ,如下图中的点 $a$ 。
2. 入度小于出度,即 $in[i]<0$ ,那么我们在图中建立边 $(i,T,-in[i])$ ,如下图中的点 $b$ 。
然后就只需要跑一次 $S$ 到 $T$ 的最大流,就可以得到可行流了。具体地,一条边的流量大小为 它的流量下界值 加上 跑完最大流后当前边的流量值 。
有源汇可行流
问题
给定源点 $s$ 和汇点 $t$ ,不要求这两个点满足流量平衡,但是其他点都要满足,求可行流。
构造方案
这类问题的构造方案就是在无源汇可行流的基础上,添加一条边 $(t,s,\infty)$ 即可。
有源汇最大流
问题
在有源汇可行流的基础上,求源点 $s$ 到汇点 $t$ 的最大流。
构造方案
首先我们跑出有源汇可行流。
记录当前流量。
删除边 $(t,s,\infty)$ ,在残量网络上求 $s$ 至 $t$ 的最大流,新增的流加上原有的流 即答案。
这里有一个小 trick : 不删除边 $(t,s,\infty)$ ,直接在残余网络上求出 $s$ 至 $t$ 的最大流即答案。为什么这样是对的?考虑在求可行流的之后,边 $(t,s,\infty)$ 的反向边的流量就是可行流大小。在求最大流的时候,我们可以把答案看成两部分:一部分是把 $(t,s,\infty)$ 的反向边流量退回去所得到的流量,一部分是原图残余网络的最大增广流量。于是,我们得到的就是这两部分的和。这样做显然省去了一些操作。
有源汇最小流
问题
在有源汇可行流的基础上,求源点 $s$ 到汇点 $t$ 的最大流。
构造方案
首先我们跑出有源汇可行流。
记录当前流量。
删除边 $(t,s,\infty)$ ,在残余网络上求 $t$ 至 $s$ 的最大流,原有的流量减去这次算出来的流量 即答案。
这个可以理解成通过最大化退流量来使得流量最小。
模板题链接
LOJ dafa好。
模板
#includeusing namespace std;typedef long long LL;struct Edge{ int x,y,nxt; LL cap; Edge(){} Edge(int a,int b,LL c,int d){ x=a,y=b,cap=c,nxt=d; }};struct Network{ static const int N=50010,M=175010*2; static const LL INF=1LL<<50; Edge e[M],tmp[M]; int n,S,T,fst[N],cur[N],cnt; int q[N],head,tail; int begin[N],end[N],vis[M]; LL MaxFlow,dis[N]; void clear(int _n){ n=_n,cnt=1; memset(fst,0,sizeof fst); } void add(int a,int b,LL c){ e[++cnt]=Edge(a,b,c,fst[a]),fst[a]=cnt; e[++cnt]=Edge(b,a,0,fst[b]),fst[b]=cnt; } void rebuild(){ memset(vis,0,sizeof vis); end[0]=1; for (int i=1;i<=n;i++){ begin[i]=(end[i]=end[i-1])+1; for (int j=fst[i];j;j=e[j].nxt){ tmp[vis[j]=++end[i]]=e[j]; if (vis[j^1]){ tmp[vis[j]].nxt=vis[j^1]; tmp[vis[j^1]].nxt=vis[j]; } } } swap(e,tmp); } void init(){ for (int i=1;i<=n;i++) cur[i]=begin[i]; } void init(int _S,int _T){ S=_S,T=_T,MaxFlow=0,init(); } int bfs(){ memset(dis,0,sizeof dis); head=tail=0; q[++tail]=T,dis[T]=1; while (head 0) g.add(S,i,in[i]),Sum+=in[i]; else if (in[i]<0) g.add(i,T,-in[i]); } bool CanFlow(){ build(); g.rebuild(); return g.Auto(S,T)>=Sum; } int cur; int GetTS(){ for (int i=2;i<=g.cnt;i++) if (g.e[g.e[i].nxt].cap==(INF>>1)) return i; return -1; } bool CanFlow(int s,int t){ build(); g.add(t,s,INF>>1); g.rebuild(); cur=GetTS(); LL v=g.Auto(S,T); return v>=Sum; } LL MaxFlow(int s,int t){ if (!CanFlow(s,t)) return -1; return g.Auto(s,t); } LL MinFlow(int s,int t){ if (!CanFlow(s,t)) return -1; LL now=g.e[cur].cap; g.e[cur].cap=g.e[g.e[cur].nxt].cap=0; return now-g.Auto(t,s); }}g;int read(){ int x=0; char ch=getchar(); while (!isdigit(ch)) ch=getchar(); while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); return x;}int n,m,S,T;signed main(){ n=read(),m=read(),S=read(),T=read(); g.clear(n); for (int i=1;i<=m;i++){ int x=read(),y=read(),L=read(),U=read(); g.add(x,y,L,U); } LL ans=g.MinFlow(S,T); if (!~ans) puts("please go home to sleep"); else printf("%lld",ans); return 0;}
鸣谢
感谢 LOJ ,提供模板题链接。
谢谢 LOJ117 运行效率榜第一的 wxh 代码供学习。
wzzlh!告诉了我那个 trick 正确的原因。我好菜啊 /kel /kel /kel。