网络流初步详解
网络流
定义
网络流图定义
- 每条边有两个权值,容量和流量
- 流量不超过每条边的容量
- 流量守恒,每个点流进来和流出去的容量一样
- 源点和汇点容量无限
定义\(E\)为边集,\(V\)为点集
最大流
\(\sum _{v \in V} f(s,v)\) 最大
真实定义:
\(|f| = \sum _{v \in V} f(s,v) - \sum_{v \in V} f(v,s)\)
由于一般构造的网络流图没有流向起点的边,所以一般不用管(但是分析的时候要考虑
问题:
-
有反向边的图如何转化使得最大流不变?
如果有二元环,新建节点连边(拆点
怎么拆?听甲鱼讲课啊QwQ
-
多源汇最大流
连一个超级源点和一个超级汇点,原来的源点汇点连的边容量为\(+inf\)
举个例子:二分图最大匹配
最大流建模
-
是否存在s到t可经过相同节点不经过相同边的两条路径
边容量设置为1
-
是否存在不经过中间相同节点的两条路径
点转化为边(拆成两个点中间连条边),容量设置为1
-
满足以上两种情况
同时设置即可
可见最大流模型的一般建模思路是运用流的容量限制,使得题目中的约束得以满足,有时还需使用一些特殊的方法(如上的拆点)来满足题目的特别约束。
残量网络
容量为原容量减去流量
还包含所有原图中的反向边,c(v,u)=-c(u,v)
走反向边的时候,相当于把原来的流量减为0
增广
定义残量网络中的一个流 f'
\(f ↑ f' (u,v)=f(u,v)+f'(u,v)-f'(v,u)\)
什么意思呢?
如果残量网络上有这么一条增广路,使得从\(s\)到\(t\)的流量最小值\(\ge 1\)(至少还有1的流量可以通过)
那么把这些流量加到最大流,更新最大流
引理
\(f ↑ f' = |f|+|f'|\)
相当于把剩下可流的流量直接流掉
这个剩下的可能是撤销原来一条弧,增加另一个地方的新弧(反向边的作用
证明
说下思路
-
流量不超过容量限制
显然,参考残量网络定义
-
流量守恒
把全部拆开,显然各自流量守恒
然后改变方向合在一起就证出来了
具体?听甲鱼讲课呀QwQ
求流量
大概是把正向子图和反向子图增广拆开
然后合并4项和两项,得到在并图中增广就可以了
增广路
残量网络中从源到汇中的一条简单路径,一个边集
流量相当于残量网络中的瓶颈,即每条边容量的最小值
结论
-
增广之后流量增加
运用上面的引理,
\(|f↑f_p| = |f| +|f_p|\)
然后对于任意增广路,\(|f_p|>1\)
不然就不事增广路力(不是一条路径
-
当找不到增广路时当前流是最大流
这个证明有点复杂
于是我们通过此问题引入新概念
割
即把\(V\)划分成\(S\)和\(T\)两个子图,定义应该是一个边集,就是\(\sum _{u\in S}\sum_{v\in T} (u,v) \and (v,u) \in E\)(超级不严谨
割的流量
\(f(S,T)=\sum _{u \in S}\sum _{v \in T} f(u,v) - \sum _{u \in S} \sum _{v \in T} f(v,u)\)
割的容量
$c(S,T)=\sum _{u \in S}\sum _{v \in T}c(u,v) $
最小割
所有割中容量最小的那个
引理2
对于流\(f\),任意割之间的流量不变
感性理解:流量守恒
还是证明一下吧
然后2,3,5,6四项相消(换一下sigma顺序就一模一样了)
得到:
\(=\sum _{u \in S}\sum _{v \in T} f(u,v) - \sum _{u \in S} \sum _{v \in T} f(v,u)\)
\(=f(u,v)\)
推论
任意流\(|f|\)的流量不超过任意割的容量
\(|f| =f(S,T)\leq c(S,T)\)
很显然
最小割最大流定理
这个定理包含三个命题:
-
流\(f\)是图\(G\)的最大流——①
-
当前流 f 的残量网络\(c_f\)上不存在增广路——②
-
存在某个割使得\(|f| =c(S, T)\)成立。由结论\(2\)可知,满足条件的割\(c\)必定是最小割——③
这三个命题等价
证明
-
证明①推②:
令④为存在\(|f_2|>|f|\)
因为\(f\)是当前的最大流,所以①成立时④不成立
参考增广路的定义,存在增广路与可以构造\(|f_2|>|f|\)(\(f\)为当前流量),即②的否命题与④成逆否命题
当①成立时,④不成立,②的否命题不成立,②成立
-
构造点集\(S\)为\(s\)在残量网络上能够到达的点集,\(T=V-S\),定义那么\(t\)一定在\(T\)中(\(T\)中至少有一个\(t\)),\((S,T)\)是一个割
即当②成立时③成立
-
先康康上面的推论
高中数学告诉我们,两边取任意,左边的最大值就等于右边的最小值
于是有最大流等于最小割
即最小割是最大流的充分条件,当③成立时①成立
由上面三个结论可以得到它们是互相的充要条件
网络流算法
Ford-Fulkerson
每次用BFS找任意一条增广路然后增加流量直到残量网络中s与t不连通
很naiive
Edmonds-Karp
每次寻找最短的增广路(BFS)
引理
增广前后s到每个点的距离不降
选取\(v\)使得\(df'(v)\) 是d下降的节点中最小的
则有\(df'(v) <df(v)\)
设u为v的前驱节点,则\(df'(u) <df'(v)\)
然后证明\((u,v)\)不在增广前的网络\(Ef\)上
如果在,\(df(v)\leq df(u)+1\leq df'(u)+1=df'(v)\)
矛盾,所以不在
因为\(df(v)\)在本次变化,所以\((u,v) \in Ef'\),
即有 \(df(v)=df(u)-1<=df'(u)-1=df'(v)-2\)
所以\(v\)不存在,其逆命题成立
EK增广\(O(nm)\)次
总\(nm^2\)
Dinic
\(dinic\) 的原理是先把原图分层,然后一次DFS找完当前残量网络中所有增广路,每次增广一个阻塞流
于是\(d\)一定单增,所以最多增广\(V\)次
又因为单次最大是\(O(VE)\)的(多次经过的点会最多被E条边更新(每条边最多被增广一次),最大V个点都这样被更新)
于是总复杂度是\(O(V^2E)\)也就是\(O(n^2m)\)
搞个板子
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#define rep(i,a,b) for(register int i=(a);i<=(b);++i)
#define CLR(x) memset(x,0,sizeof(x))
#define setmi(x) memset(x,-1,sizeof(x))
using namespace std;
const int N = 200021;
struct node{
int v,w,nex;
}edge[N<<1];
int top=1,head[N],cur[N];
const int inf=192608170;
inline void add(int u,int v,int w){
edge[++top].v=v;
edge[top].w=w;
edge[top].nex=head[u];
head[u]=top;
}
inline int read(){
char ch=getchar();int x=0;int pos=1;
for(;!isdigit(ch);ch=getchar()) if(ch=='-') pos=0;
for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
return pos?x:-x;
}
int n,m,s,t,ui,vi,wi;
class dinic{
private:
int level[N];
queue<int>q;
public:
inline int bfs(){
setmi(level);level[s]=0;
q.push(s);
while(!q.empty()){
int now=q.front();q.pop();
for(int i=head[now];i;i=edge[i].nex){
int v=edge[i].v;
if(level[v]==-1&&edge[i].w){
level[v]=level[now]+1;
q.push(v);
}
}
}
if(level[t]==-1) return 0;
else return 1;
}
inline int dfs(int now,int flow){
if(now==t||flow==0) return flow;
int res=flow;
for(register int &i=cur[now];i;i=edge[i].nex){
if(res<=0) break;
int v=edge[i].v;
if(edge[i].w&&level[v]==level[now]+1){
int away=dfs(v,min(edge[i].w,res));
res-=away,edge[i].w-=away,edge[i^1].w+=away;
}
}
cur[now]=head[now];
return flow-res;
}
inline int solve(){
int ans=0;
while(bfs()){
ans+=dfs(s,inf);
}
return ans;
}
}DI;
int main(){
n=read(),m=read(),s=read(),t=read();
rep(i,1,m){
ui=read(),vi=read(),wi=read();
add(ui,vi,wi);
add(vi,ui,0);
}
rep(i,1,n){
cur[i]=head[i];
}
printf("%d",DI.solve());
return 0;
}
中等常数,最大点64ms,不想卡
LCT优化
不会,待填坑
加上之后炒鸡块!(也就\(nm\log n\))
当前弧优化
显然每条边只会增广一次,更新head数组即可
具体做法是复制一个head的cur(head分层时还有用)
然后这样写:for(int &i = cur[now] ; i ; i = edge[i].nex)
单位容量网络
-
增广次数不超过\(E^{\frac{1}{2}}\)
-
\(d(t)\le E^{\frac{1}{2}}\)
增广至少\(E^{\frac{1}{2}}\)次
-
\(d(t) \geq E^{\frac{1}{2}}\)
在递归到第二步时,至少已经走了\(E^{\frac{1}{2}}\)层
所以至少有两层之间边数不超过\(E^{\frac{1}{2}}\),又因为每条边容量为1,即边数为两层之间的割的容量\(>\)割的流量\(=\)残量网络最大流
所以\(d\)的递增不超过\(E^\frac{1}{2}\)次,即增广次数
-
-
增广次数不超过\(V^{\frac{2}{3}}\)
-
\(d(t)\leq V^{\frac{2}{3}}\)
-
\(d(t)\geq V^{\frac{2}{3}}\)
类似地,一定存在相邻的两层满足点数\(\leq V^{\frac{1}{3}}\)
于是边数一定\(\leq (V^{\frac{1}{3}})^2=V^{\frac{2}{3}}\)
同上,容量\(>\)最小割等于最大流,于是增广不超过\(V^{\frac{2}{3}}\)
-
于是增广次数小于\(min(V^{\frac{2}{3}},E^{\frac{1}{2}})\)
然后经过每个点和每条边经过的增广路数量不超过 \(E\) 个
于是总复杂度\(O(E*min(V^{\frac{2}{3}},E^{\frac{1}{2}}))\)
单位网络
-
\(d(t)\leq V^{\frac{1}{2}}\)次
-
\(d(t) \geq V^{\frac{1}{2}}\)
类似,可以找到大小\(\leq V^{\frac{1}{2}}\)的割
可以证明跑二分图匹配复杂度
例题
最大权闭合子图
选子图相当于把一个图割成两部分,代表选或者不选
建边方式:所有的正权值连S,负权值连T,原图边连inf(防止被割
最小割产生的图S和图T,图S为最大权闭合子图
证明:
割集中所有的边,不是连接在s上,就是连接在t上
我们记割集中,所有连接在s上的边的权值和为\(x_1\),所有连接在t上的边的权值和为\(x_2\),而割集中所有边权值和为\(X=x_1+x_2\);
令图S中所有点的权值和为\(W\),记其中正权值之和为\(w_1\),负权值之和为 \(-w_2\),故\(W=w_1-w_2\);
而\(W+X=w_1-w_2+x_1+x_2\),又因为\(x_2=w_2\),\(W+X = w_1 + x_1\)
而显然的,\(w1+x1\)是整个图中所有正权值之和,记为\(SUM\)
然后有\(W = SUM - X\),“图S中所有点的权值和” = “整个图中所有正权值之和” - “割集中所有边权值和”
然后,因为SUM为定值,只要我们取最小割,则“图S中所有点的权值和”就是最大的,即此时图S为图S为最大权闭合子图
这谁想得到啊,,,
海拔
左上点海拔0,右下1,显然是左上一堆点都是0,右下一堆是1,然后取0和1交界处最小值就可以了,暴力DINIC会T
可以转对偶图然后跑最短路
后记
什么?你问我为什么没有后面的题了?
当然是因为我只是一个刚拿到普及奖的蒟蒻啊QwQ
集训以来首次自闭祭
先去搞字符串了