返回顶部

最大流

网络流初步

对于一张N个点、M条边的有向图(网络),以及源点S汇点T

边的容量

现将\(c(x,y)\)定义为每条边能通过的最大容量。

流函数

流函数\(f(x,y)\)定义为边的流量。显然,它满足三个条件

  1. \(f(x,y)=-f(y,x)\)即正向边的流量=反向边的流量。

  2. \(f(x,y)≤c(x,y)\)即每条边的流量不可能大于该边的容量。

  3. 对于所有的非源点和汇点的结点u,v:\(\sum_{(u,v)\in E}f(u,v) = \sum_{(v,u)\in E}f(v,u)\),即正向边的所有流量和=反向边的流量和。

在容量网络中,称顶点序列\((u_1,u_2,u_3,u_4,…,u_n,v)\)为一条链要求相邻的两个顶点之间有一条弧.

设P是容量网络G中一条从Vs到Vt的链,约定从Vs指向Vt的方向为正方向.在链中并不要求所有的弧的方向都与链的方向相同.

  • 前向弧:(方向与链的正方向一致的弧),其集合记为P+,

  • 后向弧:(方向与链的正方向相反的弧),其集合记为P-.

增广路

设f是一个容量网络G中的一个可行流,P是从Vs到Vt 的一条链,若P满足以下条件:

  • P中所有前向弧都是非饱和弧。
  • P中所有后向弧都是非零弧。

则称P为关于可行流f 的一条增广路。

沿这增广路改进可行流的操作称为增广。

残余网络

残余网络 (Residual Network) :在一个网络流图上,找到一条源到汇的路径(即找到了一个流量)后,对路径上所有的边,其容量都减去此次找到的量,对路径上所有的边,都添加一条反向边,其容量也等于此次找到的流量,这样得到的新图,就称为原图的“残余网络”。

假定有一个流网络G=(V,E),其源点为s,汇点为t,f为G中的一个流。对即诶点对u,v,定义残存容量\(cf(u,v)=c(u,v)-f(u,v)\)

零流

若网络流上每条弧上的流量都为0,则该网络流称为零流.

网络流

所有弧上流量的集合f={f(u,v)},称为该容量网络的一个网络流.

容量网络的边称为弧,弧的类型有:

  1. 饱和弧:\(f(u,v)=c(u,v)\)
  2. 非饱和弧:\(f(u,v)<c(u,v)\)
  3. 零流弧:\(f(u,v)=0\)
  4. 非零流弧:\(f(u,v)>0\)

可行流:在容量网络G中满足以下条件的网络流f,称为可行流。

a.弧流量限制条件:   0<=f(u,v)<=c(u,v);
b.平衡条件:即流入一个点的流量要等于流出这个点的流量,(源点和汇点除外);
c.斜对称性:f(u,v) = -f(v,u);

对于网络流图G,流量最大的可行流f,称为最大流

伪流: 如果一个网络流只满足弧流量限制条件,不满足平衡条件,则这种网络流为伪流,或称为容量可行流.(预流推进算法有用)

反向边的引入

下面是所有最大流算法的精华部分:引入反向边

举个例子,在下图,流量图的源点为1,汇点为4。

在这里插入图片描述

我们第一次找到了1-2-3-4这条增广路,这条路上的delta值显然是1。于是我们修改后得到了下面这个流。(图中的数字是容量)

在这里插入图片描述

这时候(1,2)和(3,4)边上的流量都等于容量了,我们再也找不到其他的增广路了,当前的流量是1。

但这个答案明显不是最大流,因为我们可以同时走1-2-4和1-3-4,这样可以得到流量为2的流。

那么我们刚刚的算法问题在哪里呢?问题就在于我们没有给程序一个后悔的机会,应该有一个不走(2-3-4)而改走(2-4)的机制。那么如何解决这个问题呢?回溯搜索吗?那么我们的效率就上升到指数级了。

而这个算法神奇的利用了一个叫做反向边的概念来解决这个问题。即每条边\((x,y)\)都有一条反向边\((y,x)\),反向边也同样有它的容量。

在第一次找到增广路之后,在把路上每一段的容量减少delta的同时,也把每一段上的反方向的容量增加delta。即

c(x,y)-=delta;c(y,x)+=delta

我们来看刚才的例子,在找到1-2-3-4这条增广路之后,把容量修改成如下:

在这里插入图片描述

这时再找增广路的时候,就会找到1-3-2-4这条可增广量,即delta值为1的可增广路。将这条路增广之后,得到了最大流2。

在这里插入图片描述

那么,这么做为什么会是对的呢?

事实上,当我们第二次的增广路走3-2这条反向边的时候,就相当于把2-3这条正向边已经是用了的流量给”退”了回去,不走2-3这条路,而改走从2点出发的其他的路也就是2-4。(有人问如果这里没有2-4怎么办,这时假如没有2-4这条路的话,最终这条增广路也不会存在,因为他根本不能走到汇点)同时本来在3-4上的流量由1-3-4这条路来”接管”。而最终2-3这条路正向流量1,反向流量1,等于没有流量。

这样,利用反向边,使程序有了一个后悔和改正的机会

文章引用:

https://blog.csdn.net/CY05627/article/details/90552189

最大流

Edmonds−Karp算法(EK算法)

时间复杂度O(\(nm^2\))

思想:不断用BFS寻找增广路并不断更新最大流量值,直到网络上不存在增广路为止。

// Luogu P3376 【模板】网络最大流
#include <bits/stdc++.h>
#define LL long long
#define N 10010
#define M 200010
using namespace std;

int n,m,S,T;
struct edge{LL v,c,ne;}e[M];
int h[N],idx=1;//从2,3开始配对
LL mf[N],pre[N];//mf存储s-v的路径上的流量上限,pre存储点的前驱边。

void add(int a,int b,int c){
  e[++idx]={b,c,h[a]};
  h[a]=idx;
}
bool bfs(){//找增广路
  memset(mf,0,sizeof mf);
  queue<int> q;
  q.push(S); mf[S]=1e9;
  while(q.size()){
    int u=q.front();
    q.pop();
    for(int i=h[u];i;i=e[i].ne){
      LL v=e[i].v;
      if(mf[v]==0 && e[i].c){
        mf[v]=min(mf[u],e[i].c);
        pre[v]=i;//存前驱边
        q.push(v);
        if(v==T)return true;//找到一条增广路
      }
    }
  }
  return false;
}
LL EK(){//累加可行流
  LL flow=0;
  while(bfs()){
    int v=T;
    while(v!=S){//更新残留网
      int i=pre[v];
      e[i].c-=mf[T];
      e[i^1].c+=mf[T];//反向边
      v=e[i^1].v;
    }
    flow+=mf[T];
  }
  return flow;
}
int main(){
  int a,b,c;
  scanf("%d%d%d%d",&n,&m,&S,&T);
  while(m--){
    scanf("%d%d%d",&a,&b,&c);
    add(a,b,c); 
    add(b,a,0);//反向边
  }
  printf("%lld\n",EK());
  return 0;
}

Dinic算法

进行BFS建立分层图,用d[x]记录每一个点的层次

求解最大流的时间复杂度为\(O(n^2m)\)

求解二分图最大匹配的时间复杂度为\(O(m\sqrt{n})\)

当前弧优化

对于一个节点x,当它在DFS中走到了第i条弧时,前i−1条弧到汇点的流一定已经被流满而没有可行的路线了

那么当下一次再访问x节点时,前i−1条弧就没有任何意义了

所以我们可以在每次枚举节点x所连的弧时,改变枚举的起点,这样就可以删除起点以前的所有弧,来达到优化剪枝的效果。

// Luogu P3376 【模板】网络最大流
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#define LL long long
#define N 10010
#define M 200010
using namespace std;

int n,m,S,T;
struct edge{LL v,c,ne;}e[M];
int h[N],idx=1; //从2,3开始配对
int d[N],cur[N];

void add(int a,int b,int c){
  e[++idx]={b,c,h[a]};
  h[a]=idx;
}
bool bfs(){ //对点分层,找增广路
  memset(d,0,sizeof d);
  queue<int>q; 
  q.push(S); d[S]=1;
  while(q.size()){
    int u=q.front(); q.pop();
    for(int i=h[u];i;i=e[i].ne){
      int v=e[i].v;
      if(d[v]==0 && e[i].c){
        d[v]=d[u]+1;
        q.push(v);
        if(v==T)return true;
      }
    }
  }
  return false;
}
LL dfs(int u, LL mf){ //多路增广
//mf是整条路对最大流的贡献
  if(u==T) return mf;
  LL sum=0;
  for(int i=cur[u];i;i=e[i].ne){
    cur[u]=i; //当前弧优化
    int v=e[i].v;
    if(d[v]==d[u]+1 && e[i].c){
      LL f=dfs(v,min(mf,e[i].c));
      e[i].c-=f; 
      e[i^1].c+=f; //更新残留网
      sum+=f; //累加u的流出流量
      mf-=f;  //减少u的剩余流量
      if(mf==0)break;//
    }
  }
  if(sum==0) d[u]=0; //残枝优化
  return sum;
}
LL dinic(){ //累加可行流
  LL flow=0;
  while(bfs()){
    memcpy(cur, h, sizeof h);
    flow+=dfs(S,1e9);
  }
  return flow;
}
int main(){
  int a,b,c;
  scanf("%d%d%d%d",&n,&m,&S,&T);
  while(m -- ){
    scanf("%d%d%d",&a,&b,&c);
    add(a,b,c); add(b,a,0);
  }
  printf("%lld\n",dinic());
  return 0;
}
posted @ 2023-11-08 16:46  supperwriter  阅读(91)  评论(0)    收藏  举报