网络流
1网络流简介
网络流(network-flows)是一种类比水流的解决问题方法,与线性规划密切相关。网络流的理论和应用在不断发展,出现了具有增益的流、多终端流、多商品流以及网络流的分解与合成等新课题。网络流的应用已遍及通讯、运输、电力、工程规划、任务分派、设备更新以及计算机辅助设计等众多领域。
2网络流基本定义
-
一个网络\(G=(V,E)\)为一张有向图,图中的每条有向边\((x,y)\in E\)都有一个权值\(c(x,y)\)称为边\((x,y)\)的容量。若\((x,y)\notin E\)则\(c(x,y)=1\),其中\(s\in V\) 称为源点,\(t\in V\)称为汇点。
-
\(f(x,y)\)称为边\((x,y)\)的流量。满足
- \(f(x,y)\leq c(x,y)\),即容量限制
- \(f(x,y)=-f(y,x)\),即斜对称
- \(\sum\limits_{(u,x)\in E}f(u,x)=\sum\limits_{(x,v)\in E}f(x,v),x\not=s,x\not=v\),即流量守恒。
-
f称为网络的流函数。\(c(x,y)-f(x,y)\)称为边\((x,y)\)的剩余流量。
-
若剩余流量大于0,则称这条边为增广路。
-
网络中所有的节点以及剩余流量大于0的边构成的子图为残量网络。
-
\(\sum\limits_{(s,v)\in E}f(s,v)\) 称为整个网络的流量。使其最大的流函数称为网络的最大流。
大多数题目让我们求到达汇点最大流量。
-
设\(d_x\)为从s到x的最小花费,满足\(d_y=d_x+1\) 的边构成的子图称为分层图,分层图为有向无环图。
可以理解为从源点灌水,每条边能流的水量有一定限制,问到达汇点的水最多为多少。
3求解最大流
3.1Edmonds—Karp算法
显然,若从s到t有一条由增广路组成的路径,即在残量网络上有一条从s到t的路径,那么就可以在更新流量。何时残量网络中不存在增广路,即在残量网络中s无法到达t,说明无法在增光,此时已经是最大流。
对于一条从s到t的增广路,它所能増广的流为该路径上所有边剩余流量的最小值,否则不符合流量守恒定律。
所以我们每次bfs找增广路,存下最小剩余流量和路径,然后更新残量网络。
因为斜对称,所以我们正向边反向边都要处理。
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ll long long
#define ull unsigned long long
#define N 5010
#define M number
using namespace std;
const ll INF=0x3f3f3f3f;
inline ll Min(ll a,ll b){
return a>b?b:a;
}
struct Edge{
ll from,to,cap,flow;
Edge(ll from,ll to,ll c,ll f) : from(from),to(to),cap(c),flow(f) {}
};
ll n,m,s,t;
struct E_Karp{
vector<Edge> edge;
vector<int> G[N];
ll a[N],p[N];
inline void clear(){
for(int i=1;i<=n;i++) G[i].clear();
edge.clear();
}
inline void addedge(ll from,ll to,ll c){
edge.push_back(Edge(from,to,c,0));
edge.push_back(Edge(to,from,0,0));
ll m=edge.size();
G[from].push_back(m-2);
G[to].push_back(m-1);
}
inline ll Maxflow(ll s,ll t){
ll flow=0;
while(1){
memset(a,0,sizeof(a));
queue<ll> q;
q.push(s);
a[s]=INF;
while(!q.empty()){
int x=q.front();q.pop();
for(int i=0;i<G[x].size();i++){
Edge& e=edge[G[x][i]];
if(!a[e.to]&&e.cap>e.flow){
a[e.to]=Min(a[e.from],e.cap-e.flow);
p[e.to]=G[x][i];
q.push(e.to);
}
}
if(a[t]) break;
}
if(!a[t]) break;
for(int u=t;u!=s;u=edge[p[u]].from){
edge[p[u]].flow+=a[t];
edge[p[u]^1].flow-=a[t];
}
flow+=a[t];
}
return flow;
}
};
E_Karp ek;
int main(){
scanf("%lld%lld%lld%lld",&n,&m,&s,&t);
for(int i=1;i<=m;i++){
ll from,to,c;
scanf("%lld%lld%lld",&from,&to,&c);
ek.addedge(from,to,c);
}
printf("%lld",ek.Maxflow(s,t));
return 0;
}
时间复杂度\(O(n^2m)\)但实际达不到这个规格,可以处理\(10^3\)~\(10^4\)的网络。
3.2dicnic
显然,EK算法每次bfs只能更新一条增广路,有可以优化的空间。
dicnic算法的主要思想是在分层图上増广流量。
流程:
- bfs在残量网络上构造分层图。
- 用dfs在分层图上寻找增广路,利用回溯更新流量+剪枝。
剪枝方法:
- 増广的流量为0说明这条边以后不可再増广。
- 不扩展已经扩展完的边(当前弧优化)
注意当前弧优化一定要放循环内部。
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define ll long long
#define ull unsigned long long
#define N 2010
#define M 50010
using namespace std;
const ll INF=0x3f3f3f3f;
inline ll read(){
ll x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
struct EDGE{
struct edge{
int to,next,w,rest;
inline void intt(int to_,int ne_,int w_,int re_){
to=to_;next=ne_;w=w_;rest=re_;
}
};
edge li[M];
int head[N],now[N],tail;
inline EDGE(){
tail=1;
}
inline void add(int from,int to,int w){
li[++tail].intt(to,head[from],w,w);
head[from]=tail;
swap(from,to);
li[++tail].intt(to,head[from],w,0);
head[from]=tail;
}
};
EDGE e;
ll n,m,s,t,ans;
int d[N];
queue<int> q;
inline bool bfs(){
memset(d,0,sizeof(d));
while(q.size()) q.pop();
q.push(s);d[s]=1;e.now[s]=e.head[s];
while(q.size()){
int top=q.front();q.pop();
for(int x=e.head[top];x;x=e.li[x].next){
int to=e.li[x].to,rest=e.li[x].rest;
if(!rest||d[to]) continue;
q.push(to);
e.now[to]=e.head[to];
d[to]=d[top]+1;
if(to==t) return 1;
}
}
if(!d[t]) return 0;
return 1;
}
inline int dicnic(int k,ll flow){
// printf("k:%d flow:%d\n",k,flow);
if(k==t) return flow;
ll rest=flow,x;
for(x=e.now[k];x&&rest;x=e.li[x].next){
ll to=e.li[x].to,re=e.li[x].rest;e.now[k]=x;
if(!re||d[to]!=d[k]+1) continue;
ll val=dicnic(to,min(rest,re));
if(!val) d[to]=0;
e.li[x].rest-=val;
e.li[x^1].rest+=val;
rest-=val;
}
return flow-rest;
}
int main(){
n=read();m=read();s=read();t=read();
for(int i=1;i<=m;i++){
int from=read(),to=read(),w=read();
e.add(from,to,w);
}
ll flow=0;
while(bfs()){
// printf("here\n");
while(flow=dicnic(s,INF)) ans+=flow;
}
printf("%lld\n",ans);
return 0;
}
时间复杂度\(O(n^2m)\)实际远远达不到,可以跑\(10^4\)~\(10^5\)
如果要跑二分图最大匹配,可以为\(O(m\sqrt n)\)
3.3ISAP
dicnic仍然有优化的空间,试想,我们能不能bfs一次呢?
流程:
-
我们利用bfs从t反向分层,设为\(h_x\)
-
如果\(h_s\ge n\),其中n为节点数,则集团说。
-
否则从s开始,在残余网络上沿\(h_u=h_v+1\)的方向前进
-
増广
-
若流量没有増广完,重贴标签,若拥有当前节点高度的节点数仅一个,结束,否则令\(h_u=\min \{(h_v+1)\times[c(x,y)-f(x,y)>0]\}\)
如果没有可行的点,令\(h_u=n\),若非源点,退回一步。若是源点,退出。
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define int long long
#define ull unsigned long long
#define N 2000
#define M 200010
using namespace std;
const int INF=0x3f3f3f3f;
inline int Min(int a,int b){
return a>b?b:a;
}
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
struct edge{
int to,next,f;
inline void intt(int to_,int ne_,int f_){
to=to_;next=ne_;f=f_;
}
};
edge li[M];
int head[N],tail=1,now[N];
inline void add(int from,int to,int f){
li[++tail].intt(to,head[from],f);
head[from]=tail;
li[++tail].intt(from,head[to],0);
head[to]=tail;
}
int n,m,s,t,ans;
int h[N],cnth[N];
queue<int> q;
inline void bfs(int t){
q.push(t);h[t]=0;cnth[0]=1;
now[t]=head[t];
while(q.size()){
int top=q.front();q.pop();
for(int x=head[top];x;x=li[x].next){
int to=li[x].to,f=li[x].f;
if(h[to]||to==t) continue;
h[to]=h[top]+1;
cnth[h[to]]++;
q.push(to);
now[to]=head[to];
}
}
}
inline int isap(int k,int flow){
// printf("%d\n",k);
if(k==t) return flow;
int rest=flow,x,minh=INF;
for(x=now[k];x&&rest;x=li[x].next){
int to=li[x].to,re=li[x].f;
if(re<=0) continue;
minh=Min(minh,h[to]);
if(h[to]+1!=h[k]) continue;
int val=isap(to,Min(rest,re));
li[x].f-=val;
li[x^1].f+=val;
rest-=val;
}
if(rest==0) return flow;
cnth[h[k]]--;
if(!cnth[h[k]]) h[s]=n+1;
if(minh==INF) h[k]=n;
else h[k]=minh+1;
cnth[h[k]]++;
return flow-rest;
}
signed main(){
n=read();m=read();s=read();t=read();
for(int i=1;i<=m;i++){
int from=read(),to=read(),f=read();
add(from,to,f);
}
bfs(t);
// printf("end\n");
while(h[s]<n) ans+=isap(s,INF);
printf("%lld\n",ans);
return 0;
}
4 最小费用最大流
在EK和dicnic中,把dfs改成spfa即可。
EK:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define ll long long
#define ull unsigned long long
#define N 100100
#define M 500100
using namespace std;
const ll INF=0x3f3f3f3f;
inline ll read(){
ll x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
inline ll Min(ll a,ll b){
return a>b?b:a;
}
struct edge{
ll f,to,next,w;
inline void intt(ll f_,ll to_,ll ne_,ll w_){
f=f_;to=to_;next=ne_;w=w_;
}
};
edge li[M*2];
ll head[N],tail=1;
inline void add(ll from,ll to,ll w,ll c){
li[++tail].intt(c,to,head[from],w);
head[from]=tail;
li[++tail].intt(0,from,head[to],-w);
head[to]=tail;
}
ll n,m,s,t,ans1,ans2;
ll fl[N],prep[N],pree[N],d[N];
bool vis[N];
queue<ll> q;
inline bool EK(){
// printf("here\n");
memset(d,INF,sizeof(d));
memset(fl,INF,sizeof(fl));
memset(vis,0,sizeof(vis));
while(q.size()) q.pop();
memset(prep,0,sizeof(prep));
memset(pree,0,sizeof(pree));
q.push(s);vis[s]=1;d[s]=0;
while(q.size()){
ll top=q.front();q.pop();vis[top]=0;
for(int x=head[top];x;x=li[x].next){
ll to=li[x].to,rest=li[x].f,w=li[x].w;
if(rest==0||d[to]<=d[top]+w) continue;
// printf("x:%d rest:%d to:%d\n",x,rest,to);
d[to]=d[top]+w;
fl[to]=Min(fl[top],rest);
prep[to]=top;pree[to]=x;
if(!vis[to]) q.push(to),vis[to]=1;
}
}
// printf("d[t]:%lld fl[t]:%lld INF:%lld\n",d[t],fl[t],INF);cin.get();
if(d[t]<INF&&fl[t]<INF) return 1;
else return 0;
}
inline void update(){
// printf("here\n");
ll now=t,nowe;
while(prep[now]){
nowe=pree[now];
now=prep[now];
// printf("now:%d nowe:%d\n",now,nowe);
li[nowe].f-=fl[t];
li[nowe^1].f+=fl[t];
ans2+=fl[t]*li[nowe].w;
}
ans1+=fl[t];
}
int main(){
// freopen("P3381_8.in","r",stdin);
// freopen("P3381_8.out","w",stdout);
n=read();m=read();s=read();t=read();
for(int i=1;i<=m;i++){
ll from=read(),to=read(),c=read(),w=read();
add(from,to,w,c);
}
while(EK()) update();
printf("%lld %lld\n",ans1,ans2);
return 0;
}
dicnic:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define ll long long
#define ull unsigned long long
#define N 50100
#define M 50100
using namespace std;
const int INF=0x3f3f3f3f;
inline ll read(){
ll x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
struct edge{
ll f,to,next,w;
inline void intt(ll f_,ll to_,ll ne_,ll w_){
f=f_;to=to_;next=ne_;w=w_;
}
};
edge li[M*2];
ll head[N],tail=1,now[N];
inline void add(ll from,ll to,ll w,ll c){
li[++tail].intt(c,to,head[from],w);
head[from]=tail;
li[++tail].intt(0,from,head[to],-w);
head[to]=tail;
}
int n,m,s,t,ans1,ans2;
int d[N];
queue<int> q;
bool vis[N];
inline bool spfa(){
memset(d,INF,sizeof(d));
memset(vis,0,sizeof(vis));
// while(q.size()) q.pop();
q.push(s);d[s]=0;vis[s]=1;now[s]=head[s];
while(q.size()){
int top=q.front();q.pop();vis[top]=0;
for(int x=head[top];x;x=li[x].next){
int to=li[x].to,w=li[x].w,rest=li[x].f;
// printf("x:%d to:%d w:%d f:%d\n",x,to,w,rest);
// printf("d[top]:%d d[to]:%d\n",d[top],d[to]);
if(!rest||d[to]<=d[top]+w) continue;
d[to]=d[top]+w;
now[to]=head[to];
if(!vis[to]) q.push(to),vis[to]=1;
}
}
// printf("d[t]: %d\n",d[t]);
if(d[t]>=INF) return 0;
return 1;
}
inline int dicnic(int k,int flow){
// printf("here\n");
if(k==t) return flow;
int rest=flow,x;
vis[k]=1;
for(x=now[k];x&&rest;x=li[x].next){
int to=li[x].to,f=li[x].f,w=li[x].w;
if(!f||d[to]!=d[k]+w||(vis[to]&&to!=t)) continue;
int val=dicnic(to,min(rest,f));
if(!val) d[to]=0;
li[x].f-=val;
li[x^1].f+=val;
rest-=val;
ans2+=val*w;
}
now[k]=x;
return flow-rest;
}
int main(){
// freopen("P3381_8.in","r",stdin);
n=read();m=read();s=read();t=read();
for(int i=1;i<=m;i++){
int from=read(),to=read(),c=read(),w=read();
add(from,to,w,c);
}
while(spfa())
ans1+=dicnic(s,INF);
printf("%d %d\n",ans1,ans2);
return 0;
}
5最小割
最小割即为最大流。
最小割指的是去掉一些边,使s到t无法连通,且去掉边的容量最小。
6技巧
6.1点边转化。
把每一个点拆成入点和出点,从入点到出点的边围护该点原来的信息,连向该点的边连到入点,从该点出去的边从出点出去,无向边互相连即可。
例题:P1345 [USACO5.4]奶牛的电信Telecowmunication
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define ll long long
#define ull unsigned long long
#define N 1100
#define M 7000
using namespace std;
const int INF=0x3f3f3f3f;
inline int Min(int a,int b){
return a>b?b:a;
}
inline ll read(){
ll x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
struct edge{
int to,next,f;
inline void intt(int to_,int ne_,int f_){
to=to_;next=ne_;f=f_;
}
};
edge li[M<<2];
int head[N<<1],now[N<<1],tail=1;
inline void add(int from,int to,int c){
li[++tail].intt(to,head[from],c);
head[from]=tail;
li[++tail].intt(from,head[to],0);
head[to]=tail;
}
int n,m,s,t,ans;
int d[N<<1];
queue<int> q;
inline bool bfs(int s){
memset(d,0,sizeof(d));
while(q.size()) q.pop();
d[s]=1;q.push(s);now[s]=head[s];
while(q.size()){
int top=q.front();q.pop();
for(int x=head[top];x;x=li[x].next){
int to=li[x].to,f=li[x].f;
if(!f||d[to]) continue;
d[to]=d[top]+1;
now[to]=head[to];
if(to==t) return 1;
q.push(to);
}
}
// if(d[t]) return 1;
return 0;
}
inline int dicnic(int k,int flow){
if(k==t) return flow;
int rest=flow,x;
for(x=now[k];x&&rest;x=li[x].next){
int to=li[x].to,re=li[x].f;
if(!re||d[to]!=d[k]+1) continue;
int val=dicnic(to,Min(rest,re));
if(!val) d[to]=0;
li[x].f-=val;
li[x^1].f+=val;
rest-=val;
}
now[k]=x;
return flow-rest;
}
int main(){
n=read();m=read();s=read();t=read();s+=n;
for(int i=1;i<=n;i++) add(i,i+n,1);
for(int i=1;i<=m;i++){
int x=read(),y=read();
add(x+n,y,INF);add(y+n,x,INF);
}
while(bfs(s)) ans+=dicnic(s,INF);
printf("%d\n",ans);
return 0;
}
6.2二者取一式问题
推荐:https://zhuanlan.zhihu.com/p/123308502
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define ll long long
#define ull unsigned long long
#define N 3000100
#define M 3000100
using namespace std;
const int INF=0x3f3f3f3f;
inline int Min(int a,int b){
return a>b?b:a;
}
inline ll read(){
ll x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
struct edge{
int to,next,f;
inline void intt(int to_,int ne_,int f_){
to=to_;next=ne_;f=f_;
}
};
edge li[M];
int head[N],tail=1,now[N];
inline int add(int from,int to,int c){
li[++tail].intt(to,head[from],c);
head[from]=tail;
li[++tail].intt(from,head[to],0);
head[to]=tail;
}
int n,m,s,t,ans,all;
int d[N];
queue<int> q;
inline bool bfs(int s){
memset(d,0,sizeof(d));
while(q.size()) q.pop();
d[s]=1;q.push(s);now[s]=head[s];
while(q.size()){
int top=q.front();q.pop();
for(int x=head[top];x;x=li[x].next){
int to=li[x].to,f=li[x].f;
if(!f||d[to]) continue;
d[to]=d[top]+1;
now[to]=head[to];
q.push(to);
// if(to==t) return 1;
}
}
if(d[t]) return 1;
return 0;
}
inline int dicnic(int k,int flow){
if(k==t) return flow;
int rest=flow,x;
for(x=now[k];x&&rest;x=li[x].next){
int to=li[x].to,re=li[x].f;
if(!re||d[to]!=d[k]+1) continue;
int val=dicnic(to,Min(rest,re));
if(!val) d[to]=0;
li[x].f-=val;
li[x^1].f+=val;
rest-=val;
}
now[k]=x;
return flow-rest;
}
int main(){
n=read();s=n+1;t=n+2;
for(int i=1;i<=n;i++){
int x=read();
add(s,i,x);
all+=x;
}
for(int i=1;i<=n;i++){
int x=read();
add(i,t,x);
all+=x;
}
m=read();
int now=n+2;
for(int i=1;i<=m;i++){
int k=read(),ca=read(),cb=read();
all+=ca;all+=cb;
now++;add(s,now,ca);now++;add(now,t,cb);
for(int j=1;j<=k;j++){
int p=read();
add(now-1,p,INF);add(p,now,INF);
}
}
while(bfs(s)) ans+=dicnic(s,INF);
printf("%d\n",all-ans);
return 0;
}
7引用
- 百度百科
- 算法进阶训练指南
- 趣学算法