网络流
网络流最大流
有向图 \(G\) 中,有两个特殊的点,源点和汇点,每条边有指定的容量,求 \(S\) 到 \(T\) 的最大流。
就像从源点放水,水量无穷大,汇点的水量是多少?
定义
\(c\) 为容量,\(f\) 为流量
流量守恒
\(f(x,y)\leq c(x,y)\)
容量性质
\(\sum f(u,x) = \sum f(x,u)\)
斜对称性
\(f(x,y) = -f(y,x)\)
容量网络,流量网络
残留网络=容量网络-流量网络,连两条边
增广路:残留网络上从源点到汇点的简单路径
反向弧:给算法“返悔的机会”
增广路定理:当前流为最大流的充要条件是无增广路
Ford-Fulkerson 方法
Edmonds-Karp 算法
残留流量为正,BFS
\(O(VE^2)\)
Dinic 算法
分层网络,用DFS一次求解多个增广路
BFS分层,DFS多路增广,当前弧优化:一个点被dfs两次,而接上去的前几个点无法增广,所以记录当前弧
正常时 \(O(V^2E)\)
二分图时 \(O(\sqrt{V}E)\)
实现
斜对称修改,u->v的边要记录v->u的边的下标,所以链式前向星只需要把下标换成从2开始,异或1 tot=1
两个优化:当前弧优化,剪枝
bool dinic_bfs(){
rep(i,1,T)h[i]=oh[i],level[i]=0;
queue<int> q;
level[S]=1;
q.push(S);
while(!q.empty()){
int x=q.front();q.pop();
for(int i=h[x];i;i=e[i].ne){
int y=e[i].to;
if(!level[y]&&e[i].w>EPS){
level[y]=level[x]+1;
q.push(y);
}
}
}
return level[T]!=0;
}
double dinic_dfs(int x,double cp){
if(x==T)return cp;
double tmp=cp;
for(int &i=h[x];i;i=e[i].ne){//邻接表 当前弧优化
int y=e[i].to;
if(level[y]==level[x]+1&&e[i].w>EPS){
double t=dinic_dfs(y,min(tmp,e[i].w));//记住流量要实时减少tmp而非cp
if(t<EPS)level[y]=0;//剪枝
tmp -= t;
e[i].w -= t;
e[i^1].w += t;
if(tmp<EPS)break;//当前弧更换要注意可能是tmp不够了而不是无法增广,所以不能放在for的条件判断上
}
}
return cp-tmp;
}
double dinic(){
double res=0;
while(dinic_bfs())res+=dinic_dfs(S,INF);
return res;
}
实际应用(建模)
常用构图方式
S->T流对应原题中的方案
拆点
餐厅一题:拆点
状态表示优化
猪圈问题:一轮建m+1个点,顾客和m个猪圈,跑最大流
优化:一个好几轮没打开的猪圈,出边入边相同,可以合并
多个人使用同一个猪圈,则在他们顾客之间连无穷大的边,省去猪圈的点,和很多很多边,然后从顾客连出去边,符合题意中“特定数量”
行列建点
打扫卫生:行列建点
转化为二分图最小顶点覆盖
马:坏的格子不用连,二分图最大独立集
割
流网络 割 \((S,T)\) 分成 \(S\) 和 \(T=V-S\) 两个部分
割的容量不算反方向,净流量算反方向
最大流最小割定理
此处参考《算法导论》。
首先,抛出几条引理:
\(引理1:设(S,T) 为网络 G 的一个割,横跨割的净流量等于从SS流出的总流量|f|\)
\(证明:可以结合 |f| 的定义和流量守恒的性质,以及净流量的定义的证,但是提供一种更容易理解的方法。|f|表示S要流这么多到T。割出来这一部分后,将S看作一个整体那么多了|f|流量,T少了|f|流量,因为流量守恒,所以净流量为|f|。\)
\(引理2:流网络的任意 f 的值不超过G任意一个割的容量。\)
\(证明:可以使用放缩法的证,但是读者自证不难。\)
然后命题如下
这三个命题是等价的:
1.\(f\) 是 \(G\) 的一个最大流
2.残余网络中无增广路
3.\(|f|=c(S,T)\),\((S,T)\) 为某切割
证明:
\(1\rightarrow 2\):如果有增广路,最大流就不是最大流了。
\(2\rightarrow 3\):没有增广路,按照 \(G_f\) 的关于 \(SS\) 的是否可达分为 \((S,T)\)。说明对于任意 \(u\in S,v\in T\),若 \((u,v)\in E\),则 \(f(u,v)=c(u,v)\);若 \((v,u)\in E\),则 \(f(v,u)=0\)。所以 \(|f|=f(S,T)=c(S,T)\)。
\(3\rightarrow 1\):因为 \(|f|_{max}\leq c(S,T)_{min}\),所以 \(f\) 一定是最大流。
二分图性质
二分图匹配,选边,两两间没有公共点(匹配)
二分图最小顶点覆盖,选点,每条边至少有一个端点被选出(覆盖集)
二分图最大独立集,选点,点两两间没有边相连(独立集)
-
因为网络流的模型相同,根据最大流最小割定理,二分图最小点覆盖等于最大匹配。
-
二分图最大独立集等于总点数减去最小覆盖集。
通过反证,先证充分性,去掉独立集如果不是覆盖集,那么独立集间有边相连,矛盾。再证必要性,覆盖集一定占据了任意一条边的一个端点,那么独立集一定没有相连的边。(充分必要逻辑有问题)
最大权闭合子图问题
一些项目做了会赚钱,一些员工雇佣要花钱。一个项目需要指定的员工,一个员工雇佣后可以参加任意多项目,问经过项目取舍,最多可以挣多少钱?
可以建模成图,一个点选了,则出边都要选。建立网络流模型,原点向项目连边,员工向汇点连边,中间边权无穷大。应当在图上求最小割。
如果 \(S->P\) 没有被割且 \(P->Q->T\) 没有被割,则选了项目没有选人,不合法,而建模与题意吻合,跑最小割。
最小费用最大流
每次增广挑费用最小的路,每次得到的流都是最小费用,最后得到最小费用最大流
反向边将费用取反
SSP 算法
复杂度伪多项式时间,建议加各种优化
int h[NN],tot;
struct Edge{
int ne,to,w,c;
}e[MM];
void add(int x,int y,int z,int c){
e[++tot]={h[x],y,z,c};
h[x]=tot;
}
int vis[NN],dis[NN],pre[NN];
bool SPFA(){
rep(i,1,T)dis[i]=INF,vis[i]=0;
queue<int> q;
q.push(S);
dis[S]=0;
while(!q.empty()){
int x=q.front();q.pop();
vis[x]=0;
for(int i=h[x];i;i=e[i].ne){
int y=e[i].to,c=e[i].c;
if(e[i].w>0&&dis[y]>dis[x]+c){
dis[y]=dis[x]+c;
pre[y]=i;
if(!vis[y])vis[y]=1,q.push(y);
}
}
}
return dis[T]!=INF;
}
int MCMF(){//min cost max flow
int cost=0,maxflow=0;
while(SPFA()){
int x=INF,co=0;
for(int i=pre[T];i;i=pre[e[i^1].to])
x=min(x,e[i].w),co+=e[i].c;
for(int i=pre[T];i;i=pre[e[i^1].to])
e[i].w -= x,e[i^1].w += x;
maxflow += x;
cost += co*x;
}
return cost;
}
复杂度
复杂度:未知
上下界最小费用可行流
将下界一定会流满,所以先算下界流满的情况。注意到有一些节点不满足流量平衡性质,进行分类讨论
-
流入大于流出
-
流出大于流入
记作 \(f_i\) 大于零情况1,小于零情况2
建立差网络,流量为上界流量减下界流量,则将原来网络进行了拆分。可以保证流量限定。
接着讨论流量平衡,下界网络流入大于流出,则差网络要流出大于流入。反之亦然。然后建立源汇,在差网络上求可行流,需要从流量不平衡的点向源汇连边。多流出 \(f_i\) 的在差网络要多流入 \(f_i\) ,向汇点连边,不计费用。反之亦然。从而使去掉源汇及其边的差网络满足上述条件。
练习题
P4043 [AHOI2014/JSOI2014] 支线剧情 提示:上下界最小费用可行流
建模总结
数学研究性质,信息解决问题。
数学善于建立定理,概念。理念和建模是两码事,要符合实际问题。
常用技巧:
-
拆点,如果一个点无法完全表示题目信息
-
行列建点,对于网格图
-
重构状态,发现问题分析走向死胡同
-
序列建立虚时间轴,对于区间操作

浙公网安备 33010602011771号