文章目录
网络流
定义
N = ( V , E , c , X , Y ) N=(V,E,c,X,Y) N=(V,E,c,X,Y)是一个网络
1. G ( V , E ) G(V,E) G(V,E)为点集为 V V V边集为 E E E的有向图
2. c c c是 E E E上的非负函数,称为容量函数,对于每条边 e e e, c ( e ) c(e) c(e)表示边 e e e的容量
3. X , Y X,Y X,Y为两个非空不交子集, X X X为发点集, X X X中点为源, Y Y Y为收点集, Y Y Y中点为汇, X , Y X,Y X,Y之外点集为中间点。(多源多汇可转化为单源单汇,下面均讨论单源单汇问题, s s s, t t t为源汇)
4. f f f为流量函数, f ( e ) f(e) f(e)表示边 e e e的流量,流的价值等于源的出流量等于汇的入流量。
5. A ⊆ V , A ′ = V − A , s ∈ A , t ∈ A ′ , ( A , A ′ ) A \subseteq V,A'=V-A,s \in A,t \in A',(A,A') A⊆V,A′=V−A,s∈A,t∈A′,(A,A′)中的边集称为 N N N的一个割。
性质
1.容量限制, 0 ≤ f ( e ) ≤ c ( e ) 0 \leq f(e) \leq c(e) 0≤f(e)≤c(e)
2.流量守恒,中间点入流等于出流,源的出流等于汇的入流。
3.最大流最小割定理:网络流的最大流等于最小割。
证明:
1)任意一个割大于任意一个流,即任意割大于最大流。根据流量守恒,总流量=A中的出流量和=A’中入流量和<=(A,A’)中边权和=割。
2)存在一个割等于最大流。若存在增广路则未达到最大流,则最大流时不存在增广路,此时(X,Y)之间边集即为最小割。
算法
EK
算法原理:残量网络中不存在增广路时达到最大流。
证明:
1.显然,存在增广路时未达到最大流。
2.不存在增广路时,残量网络中s和t不连通,即(X,Y)间边集为一个割,由流量守恒得也为一个流,根据最大流最小割定理,任意割大于等于最大流,即若割等于流,则流为最大流。
算法过程:不断bfs找到最短的增广路进行增广,直到不存在增广路为止。
时间复杂度: O(V*E^2)
证明:
1)每次增广后,任意点到源点的距离不减。
反证:dis(x)为增广前x到源点的距离,dis’(x)为增广后到源点的距离。第一次有点到源点距离减少时,设
v为增广后距源点最近的距离减少的点,即dis’(v)<dis(v)。
设dis’(v)=dis’(u)+1,即dis’(u)<dis’(v),则dis’(u)>=dis(u), dis’(v)>=dis(u)+1
若增广前存在边(u,v),则dis(v)<=dis(u)+1 即dis’(v)>=dis(v),矛盾
若增广前不存在边(u,v),则此次增广流过边(v,u),即dis(u)=dis(v)+1,即dis’(v)>=dis(v)+2,矛盾
2)设一条增广路上残量最小的边为关键边,一次增广至少经过一条关键边。
3)一条关键边经增广后即消失,设边(u,v)成为关键边时,dis(v)=dis(u)+1,(u,v)重新出现时,即增广路经过(v,u),
设此时dis’(u)=dis’(v)+1,由1)知dis’(u)=dis’(v)+1>=dis(v)+1=dis(u)+2,即u到源点距离至少增加2。所以每条边最
多成为关键边V/2次,共E×V/2条关键边,至多増广E×V/2。每次増广bfs复杂度为O(E),总复杂度O(V*E^2)。
Dinic
EK中每次bfs后沿到汇点距离最小的路径増广,然而距离最小的路径并不一定只有一条,每次都重新bfs很浪费,于是思考能不能支持多路径増广。
每次bfs后,用dfs沿着最短路径一直増广直到不存在增广路,这样可以把到汇点距离最短的所有路径全部(重新分层后所有最短路不减,显然可以全部)増广(每次増广至少减少一条边,每条增广路最长为V,时间复杂度为V*E。),下次bfs时到汇点的最短路径一定会增加,那么最多bfs V次,总复杂度为V^2×E.
单位流量时复杂度为O(E*sqrt(V))
ISAP
之前一直写非递归版没有发现,其实ISAP就是加了优化的Dinic,递归版几乎一样。
Dinic中每次重新分层时其实改变的地方并不多,有时改动很小还是要花大工夫bfs整个图,很不划算。我们只在最开始bfs整张图分层,且从t开始标号,然后在dfs増广的同时对分层进行改变。当此次dfs使当前点和Y集合断开时,给当前点选一个有边相连而标号最小的与Y相连的点的标号+1作为新标号即可。
//Achen
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=200007;
typedef long long LL;
typedef double db;
using namespace std;
int n,m,s,t;
template<typename T> void read(T &x) {
char ch=getchar(); x=0; T f=1;
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=-1,ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}
struct edge {
int fr,to,cap,fl,nx;
}e[N];
int fir[N],ecnt=1;
void add(int u,int v,int w) {
e[++ecnt]=(edge){u,v,w,0,fir[u]}; fir[u]=ecnt;
e[++ecnt]=(edge){v,u,0,0,fir[v]}; fir[v]=ecnt;
}
queue<int>que;
int d[N],cur[N],c[N];
void bfs(int s,int t) {
For(i,1,n) d[i]=n;
d[t]=0;
que.push(t);
while(!que.empty()) {
int x=que.front();
que.pop();
for(int i=fir[x];i;i=e[i].nx) if(e[i].cap==0) {
int y=e[i].to;
if(d[y]==n) {
d[y]=d[x]+1;
que.push(y);
}
}
}
}
int pr[N];
#define inf 1e18
LL calc(int s,int t) {
LL fl=inf;
for(int x=t;x!=s;x=e[pr[x]].fr)
fl=min(fl,(LL)e[pr[x]].cap-e[pr[x]].fl);
for(int x=t;x!=s;x=e[pr[x]].fr)
e[pr[x]].fl+=fl,e[pr[x]^1].fl-=fl;
return fl;
}
LL isap(int s,int t) {
For(i,0,n) c[i]=0;
bfs(s,t);
For(i,1,n) cur[i]=fir[i],c[d[i]]++;
LL rs=0;
for(int x=s;d[x]<n;) {
if(x==t) {
rs+=calc(s,t);
x=s;
}
int ok=0,D=n;
for(int &i=cur[x];i;i=e[i].nx) if(d[e[i].to]+1==d[x]&&e[i].cap>e[i].fl) {
ok=1; pr[x=e[i].to]=i; break;
}
if(!ok) {
cur[x]=fir[x];
for(int i=fir[x];i;i=e[i].nx) if(e[i].cap>e[i].fl)
D=min(D,d[e[i].to]+1);
if(!(--c[d[x]])) break;
c[d[x]=D]++;
if(x!=s) x=e[pr[x]].fr;
}
}
return rs;
}
int main() {
//freopen("1.in","r",stdin);
//freopen("1.out","w",stdout);
read(n); read(m); read(s); read(t);
For(i,1,m) {
int u,v,w;
read(u); read(v); read(w);
add(u,v,w);
}
printf("%lld\n",isap(s,t));
Formylove;
}
最小费用最大流
用EK算法实现最小费用最大流,把bfs换成spfa,复杂度的证明可以参考EK算法。
证明:当原图不存在负费用圈时,每次通过最短路更新得到的一定是当前流量下的最小费用。
数学归纳法,设f(x)表示流量为x时的一个最小费用流且f(x)不存在负费用圈。f(0),即原图不存在负费用圈。
当已知f(x),设f(x+1)是f(x)的基础上沿最短路増广,f(x+1)-f(x)即为一条从s到t的路径。若是存在f’(x+1)使f’(x+1)小于f(x+1),在网络f’(x+1)-f(x)中,除了源汇出入流为1,其余点出入流为0,即一条从s到t的路径加若干圈。因为f’(x+1)<f(x+1),即f(x)中存在负费用圈,与题设矛盾。得证。
其他:当前流为最小费用流,当且仅当残量网络中不存在负费用圈。
P3381 【模板】最小费用最大流
把spfa换成dijkstra可提高效率。给每人一个势能函数d,每次d+=上次的最短路,使边权变为w(u,v)+d(u)-d(v)。具体可见模板里的题解。类似的方法可以做全源最短路。
//Achen
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=100007;
typedef long long LL;
typedef double db;
using namespace std;
int n,m,s,t;
template<typename T> void read(T &x) {
char ch=getchar(); x=0; T f=1;
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=-1,ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}
struct edge {
int fr,to,cap,fl,cost,nx;
}e[N];
int fir[N],ecnt=1;
void add(int u,int v,int w,int co) {
e[++ecnt]=(edge){u,v,w,0,co,fir[u]}; fir[u]=ecnt;
e[++ecnt]=(edge){v,u,0,0,-co,fir[v]}; fir[v]=ecnt;
}
#define inf 1e9
LL d[N],sd[N];
struct node {
int x; LL d;
friend bool operator <(const node&A,const node&B) {
return A.d>B.d;
}
};
priority_queue<node>que;
int pr[N],vis[N];
int spfa(int s,int t) {
For(i,1,n) d[i]=inf;
d[s]=0;
que.push((node){s,0});
while(!que.empty()) {
node t=que.top();
que.pop(); int x=t.x;
if(d[x]!=t.d) continue;
for(int i=fir[x];i;i=e[i].nx) if(e[i].cap>e[i].fl) {
int y=e[i].to;
if(d[y]>d[x]+e[i].cost+sd[x]-sd[y]) {
d[y]=d[x]+e[i].cost+sd[x]-sd[y];
pr[y]=i;
que.push((node){y,d[y]});
}
}
}
return d[t]!=inf;
}
LL rs1,rs2;
void calc(int s,int t) {
LL fl=inf,co=0;
for(int x=t;x!=s;x=e[pr[x]].fr)
fl=min(fl,(LL)e[pr[x]].cap-e[pr[x]].fl);
for(int x=t;x!=s;x=e[pr[x]].fr)
e[pr[x]].fl+=fl,e[pr[x]^1].fl-=fl;
rs1+=fl; rs2+=(d[t]-sd[s]+sd[t])*fl;
}
void EK(int s,int t) {
For(i,1,n) sd[i]=0;
while(spfa(s,t)) {
calc(s,t);
For(i,1,n) sd[i]+=d[i];
}
printf("%lld %lld\n",rs1,rs2);
}
int main() {
//freopen("1.in","r",stdin);
//freopen("1.out","w",stdout);
read(n); read(m); read(s); read(t);
For(i,1,m) {
int u,v,w,co;
read(u); read(v); read(w); read(co);
add(u,v,w,co);
}
EK(s,t);
Formylove;
}
有上下界的网络流
无源汇可行流
要求一个满足上下界的可行解,先强行让所有下界流过去,但此时不满足流量守恒,我们再来构造一个守恒的合法的解。(u,v)下界为dn,则建立超级源ss向v连容量为dn的边,u向超级汇tt连容量为dn的边,跑ss,tt最大流,若新建边全满流,则存在可行解。若为费用流,则原边保留原费用,新建边费用为0即可。
有源汇的可行流
因为s和t不满足流量守恒,添加边(t,s)容量为 i n f inf inf,再做无源汇的可行流即可。
有源汇的最大/小流
最大流:构造出可行流后,再在残量网络上跑s,t最大流即可,此时中间点始终满足流量守恒且符合上下界。
最小流:构造出可行流后,再在残量网络上跑t,s最大流,即尽可能地退流即可。
常见模型
二分图最小割模型
题目:
三分图最小割模型:
题目:
最大权闭合图
定义一个有向图的闭合图是该有向图的一个点集,且该点集的所 有出边都还指向该点集。闭合图不一定是一个连通块。
给每个点分配一个权值,权值和最大的闭合图被称为最大权闭合图。
转化为最小割模型,对于原图中的边(u,v)连边(u,v)容量为 i n f inf inf,源点向权值为正的点连边容量为 v v v,权值为负的点向汇点连边容量为 − v -v −v。
显然割边一定是与源或汇相连的边,且容量为 i n f inf inf的边限制了两点必须在同一个集合,和源在一个集合的点即为所选点, a n s = ∑ v ≥ 0 , v ∈ X v − ∑ v < 0 , v ∈ X ( − v ) = ∑ v ≥ 0 v − ∑ v ≥ 0 , v ∈ Y v − ∑ v < 0 , v ∈ X v = ∑ v ≥ 0 v − c u t ( X , Y ) ans=\sum_{v \geq 0,v \in X}v-\sum_{v<0,v \in X}(-v)=\sum_{v \geq 0}v-\sum_{v \geq 0,v \in Y}v-\sum_{v<0,v \in X}v=\sum_{v \geq 0}v-cut(X,Y) ans=∑v≥0,v∈Xv−∑v<0,v∈X(−v)=∑v≥0v−∑v≥0,v∈Yv−∑v<0,v∈Xv=∑v≥0v−cut(X,Y)
最大密度子图
无向图的子图的密度: D = ∣ E ∣ ∣ V ∣ D=\frac{|E|}{|V|} D=∣V∣∣E∣
分数规划问题,设 g ( λ ) = ∣ E ∣ − λ ∣ V ∣ g(\lambda)=|E|-\lambda|V| g(λ)=∣E∣−λ∣V∣,二分 λ \lambda λ,求 g g g.
法1.边拆成点,权值为1,点权值为 − λ -\lambda −λ,选边的条件是两个点都必须选,转化为最大权闭合图,图大小为
( E + V , E + V ) (E+V,E+V) (E+V,E+V) ,复杂度较高。
法2. m a x ( ∣ E ∣ − λ ∣ V ∣ ) = − m i n ( λ ∣ V ∣ − ∣ E ∣ ) = − m i n ( ∑ v ∈ X λ − ∑ v ∈ X d − c u t ( X , Y ) 2 ) max(|E|-\lambda|V|)=-min(\lambda|V|-|E|)=-min(\sum_{v \in X}\lambda-\frac{\sum_{v \in X}d-cut(X,Y)}{2}) max(∣E∣−λ∣V∣)=−min(λ∣V∣−∣E∣)=−min(∑v∈Xλ−2∑v∈Xd−cut(X,Y))
= − 1 2 ∗ m i n ( ∑ v ∈ X ( 2 λ − d ) + c u t ( X , Y ) ) =-\frac{1}{2}*min(\sum_{v \in X}(2\lambda-d)+cut(X,Y)) =−21∗min(∑v∈X(2λ−d)+cut(X,Y))
这一步就转化为最基本的文理分科模型, s s s-> v : 0 v:0 v:0,v: v v v-> t : 2 λ − d t:2\lambda-d t:2λ−d, u u u-> v v v:1, v v v-> u u u:1。为避免出现负数,给与
源汇相连的边容量都加上一个很大的正数 U U U
最大密度子图带边权的推广
只需把度数 d d d换成关联边的边权和,原图中的边容量为原边权即可。此时二分的复杂度会偏高。
最大密度子图带边权和点权的推广
D = ∑ V a l v + ∑ V a l e ∣ V ∣ D=\frac{\sum Val_v+\sum Val_e}{|V|} D=∣V∣∑Valv+∑Vale
m a x ( ∑ V a l v + ∑ V a l e − λ ∣ V ∣ ) = − m i n ( λ ∣ V ∣ − ∑ V a l v − ∑ V a l e ) = − m i n ( ∑ v ∈ X ( λ + V a l x ) − ∑ v ∈ X d − c u t ( X , Y ) 2 ) max( \sum Val_v+\sum Val_e-\lambda|V|)=-min(\lambda|V|-\sum Val_v-\sum Val_e)=-min(\sum_{v \in X}(\lambda+Val_x)-\frac{\sum_{v \in X}d-cut(X,Y)}{2}) max(∑Valv+∑Vale−λ∣V∣)=−min(λ∣V∣−∑Valv−∑Vale)=−min(∑v∈X(λ+Valx)−2∑v∈Xd−cut(X,Y))
同理前两种模型。
二分图相关
1.二分图中最大流等于最大匹配数
2.二分图最小顶点覆盖等于最大匹配
3.二分图最大独立集=所有顶点数-最小顶点覆盖=最小边覆盖
4.二分图最小路径覆盖数=点数-最小路径覆盖中的边数=点数-最大匹配
网络流求解混合图欧拉回路
基于流量守恒的网络流模型。
存在欧拉回路的条件是所有点入度等于出度,先将所有无向边任意定向,出度大于入度的点缺少入度,从源向该点连容量为(出度-入度)/2的边,出度小于入度的点缺少出度,从其向汇连容量为(入度-出度)/2的边,原本定向(u,v)的边连u->v容量为1,一条从源到汇流经(u,v)的流代表改变边(v,u)的方向,此时u的入度增加,v的出度增加。故当所有点连向源汇的边满流时有解,满流的中间边即为需要改变方向的边。
最小割树
有结论,无向图任意两点间的最小割只有n-1个。
每次取两点做最小割,最小割把点集分成两个集合,用这个最小割更新集合间的最小割,再对每个集合递归处理。
没有证明,这篇写的东西太多啊宸实在证不下去了。
2229: [Zjoi2011]最小割
//Achen
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<queue>
#include<cmath>
const int N=157,inf=0x7fffffff;
typedef long long LL;
using namespace std;
int T,n,m,a[N],b[N],dis[N][N];
template<typename T>void read(T &x) {
char ch=getchar(); x=0; T f=1;
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=-1,ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}
struct edge {
int u,v,fl,cap,nx;
edge(){}
edge(int u,int v,int fl,int cap,int nx):u(u),v(v),fl(fl),cap(cap),nx(nx){}
}e[N*N];
int fir[N],cur[N],ecnt,p[N];
void add(int u,int v,int cap) {
e[++ecnt]=edge(u,v,0,cap,fir[u]); fir[u]=ecnt;
e[++ecnt]=edge(v,u,0,0,fir[v]); fir[v]=ecnt;
}
int calc(int s,int t) {
int fl=inf;
for(int i=t;i!=s;i=e[p[i]].u)
fl=min(fl,e[p[i]].cap-e[p[i]].fl);
for(int i=t;i!=s;i=e[p[i]].u)
e[p[i]].fl+=fl,e[p[i]^1].fl-=fl;
return fl;
}
int d[N],c[N];
queue<int>que;
int bfs(int s,int t) {
que.push(t);
d[t]=0;
while(!que.empty()) {
int x=que.front();
que.pop();
for(int i=fir[x];i;i=e[i].nx) if(d[e[i].v]==n&&e[i].cap==0) {
d[e[i].v]=d[x]+1;
que.push(e[i].v);
}
}
}
int ISAP(int s,int t) {
memset(c,0,sizeof(c));
for(int i=1;i<=n;i++) d[i]=n,cur[i]=fir[i];
bfs(s,t);
for(int i=1;i<=n;i++) c[d[i]]++;
int res=0;
for(int x=s;d[x]<n;) {
if(x==t) {
res+=calc(s,t);
x=s;
}
int ok=0;
for(int &i=cur[x];i;i=e[i].nx) if(d[e[i].v]+1==d[x]&&e[i].cap>e[i].fl) {
p[x=e[i].v]=i; ok=1; break;
}
if(!ok) {
cur[x]=fir[x]; int M=n;
for(int i=fir[x];i;i=e[i].nx) if(e[i].cap>e[i].fl)
M=min(M,d[e[i].v]+1);
if(!(--c[d[x]])) break;
c[d[x]=M]++;
if(x!=s) x=e[p[x]].u;
}
}
return res;
}
int vis[N];
void dfs(int x) {
vis[x]=1;
for(int i=fir[x];i;i=e[i].nx) if(!vis[e[i].v]&&e[i].fl<e[i].cap)
dfs(e[i].v);
}
void restore() { for(int i=2;i<=ecnt;i++) e[i].fl=0; }
void solve(int l,int r) {
if(l>=r) return;
restore();
int t=ISAP(a[l],a[r]),L=l-1,R=r+1;
memset(vis,0,sizeof(vis));
dfs(a[l]);
for(int i=1;i<=n;i++) if(vis[i])
for(int j=1;j<=n;j++) if(!vis[j])
dis[i][j]=dis[j][i]=min(dis[i][j],t);
for(int i=l;i<=r;i++)
if(vis[a[i]]) b[++L]=a[i];
else b[--R]=a[i];
for(int i=l;i<=r;i++) a[i]=b[i];
solve(l,L); solve(R,r);
}
int main() {
read(T);
while(T--) {
ecnt=1;
memset(fir,0,sizeof(fir));
memset(dis,127/3,sizeof(dis));
read(n); read(m);
for(int i=1;i<=m;i++) {
int u,v,w;
read(u); read(v); read(w);
add(u,v,w);
add(v,u,w);
}
for(int i=1;i<=n;i++) a[i]=i;
solve(1,n);
int q,xx;
read(q);
while(q--) {
read(xx);
int ans=0;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(dis[i][j]<=xx) ans++;
printf("%d\n",ans);
}
puts("");
}
return 0;
}
/*
1
5 0
1
0
*/
全局最小割
题目
同上,又是一个我只会背板的东西。
//Achen
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<queue>
#include<cmath>
const int N=507,inf=0x7fffffff;
typedef long long LL;
using namespace std;
int n,m,eg[N][N];
template<typename T>void read(T &x) {
char ch=getchar(); x=0; T f=1;
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=-1,ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}
int vis[N],dis[N],id[N];
int stoer_wagner(int n) {
int res=inf;
for(int i=0;i<n;i++) id[i]=i;
while(n>1) {
memset(vis,0,sizeof(vis));
memset(dis,0,sizeof(dis));
int pr=0;
vis[id[pr]]=1;
for(int i=1;i<n;i++) {
int k=-1;
for(int j=1;j<n;j++) if(!vis[id[j]]) {
dis[id[j]]+=eg[id[pr]][id[j]];
if(k==-1||dis[id[j]]>dis[id[k]]) k=j;
}
vis[id[k]]=1;
if(i==n-1) {
res=min(res,dis[id[k]]);
for(int j=0;j<n;j++) {
eg[id[pr]][id[j]]+=eg[id[k]][id[j]];
eg[id[j]][id[pr]]+=eg[id[j]][id[k]];
}
id[k]=id[--n];
}
pr=k;
}
}
return res;
}
int main() {
while(scanf("%d%d",&n,&m)==2) {
memset(eg,0,sizeof(eg));
for(int i=1;i<=m;i++) {
int x,y,z;
read(x); read(y); read(z);
eg[x][y]+=z; eg[y][x]+=z;
}
printf("%d\n",stoer_wagner(n));
}
return 0;
}
参考资料
算法导论p 426~427