网络流之最大流 EK/Dinic/Isap算法 学习笔记

来源:互联网 发布:学科数据分析 编辑:程序博客网 时间:2024/06/06 16:37

EK

算法流程

不停地找增广路进行增广,知道找不到增广路为止。
每一次bfs只找一条增广路。
时间复杂度O(VE2)

代码

// codevs 1993 #include<iostream>#include<cstring>#include<cstdio>#include<queue> using namespace std;const int inf=2100000000;int n,m,maxflow,a[205][205],flow[205],pre[205];//n表示边数,m表示点数,maxflow为最大流流量,a为每条边的容量,flow为每个点增广的流量,pre为增广时点的前驱 int x,y,cap;queue <int> q;inline int bfs(int s,int t){    while (!q.empty()) q.pop();    for (int i=1;i<=m;++i) pre[i]=-1;    pre[s]=0;    q.push(s);    flow[s]=inf;    while (!q.empty()){        int x=q.front();        q.pop();        if (x==t) break;        for (int i=1;i<=m;++i)          //EK一次只找一个增广路           if (a[x][i]>0&&pre[i]==-1){            pre[i]=x;            flow[i]=min(flow[x],a[x][i]);            q.push(i);          }    }    if (pre[t]==-1) return -1;    else return flow[t];}//s为源点,t为汇点 //increase为增广的流量 inline void ek(int s,int t){    int increase=0;    while ((increase=bfs(s,t))!=-1){        int k=t;        while (k!=s){            int last=pre[k];            a[last][k]-=increase;            a[k][last]+=increase;            k=last;        }        maxflow+=increase;    }}int main(){    scanf("%d%d",&n,&m);    for (int i=1;i<=n;++i){         scanf("%d%d%d",&x,&y,&cap);        a[x][y]+=cap;    }    ek(1,m);    printf("%d\n",maxflow);}

Dinic

dinic算法在EK算法的基础上增加了分层图的概念,根据从s到各个点的最短距离的不同,把整个图分层。寻找的增广路要求满足所有的点分别属于不同的层,且若增广路为s,P1,P2Pk,t,点v在分层图中的所属的层记为deepv,那么应满足deeppi=deeppi1+1

算法流程

  1. 对网络中的每一条边,将流量设置为0
  2. 对当前残量网络,构建分层图。若deept=+,则退出,输出答案。
  3. 寻找到残量网络中的一条满足分层图限制的可行流
    (即原网络中满足分层图限制的增广路)
  4. 利用寻找到的增广路『增流』 ,重复步骤2

时间复杂度

在普通情况下, DINIC算法时间复杂度为O(V2E)
在二分图中, DINIC算法时间复杂度为O(VE)

优化

• 多路增广
每次不是寻找一条增广路,而是在DFS中,只要可以就递归增广下去,实际上形成了一张增广网。
• 当前弧优化
对于每一个点,都记录上一次检查到哪一条边。因为我们每次增广一定是彻底增广(即这条已经被增广过的边已经发挥出了它全部的潜力,不可能再被增广了),下一次就不必再检查它,而直接看第一个未被检查的边。

优化之后渐进时间复杂度没有改变,但是实际上能快不少。

实际写代码的时候要注意,next数组初始值为-1,存储时从0开始存储,这样在后面写反向弧的时候比较方便,直接异或即可。

代码

// codevs 1993#include<iostream>#include<cstring>#include<cstdio>#include<queue>using namespace std;const int max_n=205;const int max_m=205;const int max_e=max_m*2;const int inf=1e9;int point[max_n],next[max_e],v[max_e],remain[max_e],deep[max_n],cur[max_n];int n,m,x,y,cap,tot,maxflow;queue <int> q;inline void add(int x,int y,int cap){    ++tot; next[tot]=point[x]; point[x]=tot; v[tot]=y; remain[tot]=cap;    ++tot; next[tot]=point[y]; point[y]=tot; v[tot]=x; remain[tot]=0;}//分层 inline bool bfs(int s,int t){    //初始化     memset(deep,0x7f,sizeof(deep));    deep[s]=0;    for (int i=1;i<=n;++i)      cur[i]=point[i];    while (!q.empty()) q.pop();    q.push(s);    while (!q.empty()){        int now=q.front(); q.pop();        for (int i=point[now];i!=-1;i=next[i])          if (deep[v[i]]>inf&&remain[i]){            deep[v[i]]=deep[now]+1;            q.push(v[i]);          }    }    return deep[t]<inf;}//找到当前点最大能够增广的flow //limit表示到目前为止走过的增广路容量最小的边 inline int dfs(int now,int t,int limit){    if (!limit||now==t) return limit;    int flow=0,f;    for (int i=cur[now];i!=-1;i=next[i]){        cur[now]=i;        if (deep[v[i]]==deep[now]+1&&(f=dfs(v[i],t,min(limit,remain[i])))){            flow+=f;            limit-=f;            remain[i]-=f;            remain[i^1]+=f;            if (!limit) break;        }    }    return flow;}inline void dinic(int s,int t){    while (bfs(s,t))      maxflow+=dfs(s,t,inf);}int main(){    tot=-1;    memset(point,-1,sizeof(point));    memset(next,-1,sizeof(next));    scanf("%d%d",&m,&n);    for (int i=1;i<=m;++i){        scanf("%d%d%d",&x,&y,&cap);        add(x,y,cap);    }    dinic(1,n);    printf("%d\n",maxflow);}

Isap

ISAP(ImprovedShortestAugmentingPath)也是基于分层思想的最大流算法。所不同的是,它省去了

Dinic每次增广后需要重新构建分层图的麻烦,而是在每次增广完成后自动更新每个点的标号(也就是所

在的层)

算法流程

  1. 利用BFS从开始反向标号(分层)。
  2. 进行递归,若当前节点i
    (1)为汇点,则进行增广,同时退回到起点准备进行新一轮增广路的寻找。
    (2)在残量网络中存在一条边(i,j)cij>0,且deepi=deepj+1(即满足分层图的要求),则

前进到j点。
3. 没有满足条件的出边,对i点重新进行标号。deepi=min{deepj|(i,j)E,cij>0},其

E为残量网络的边集。

算法结束的条件:deeps=+(类似于Dinic,即在残量网络中已不存在s到t的通路。

时间复杂度

渐进时间复杂度和dinic相同,但是非二分图的情况下isap更具优势。

优化

1、当前弧优化:和dinic相同
2、GAP优化:
numk记录当前有多少点编号为k
k[0,deeps],numk=0,则说明当前的网络不可能再被增广,可以直接退出。

代码

// codevs 1993#include<iostream>#include<cstring>#include<cstdio>#include<queue>using namespace std;const int max_n=205;const int max_m=205;const int max_e=max_m*2;const int inf=1e9;int point[max_n],next[max_e],v[max_e],remain[max_e],tot;int cur[max_n],deep[max_n],last[max_n],num[max_n];int n,m,x,y,cap,maxflow;queue <int> q;inline void add(int x,int y,int cap){    ++tot; next[tot]=point[x]; point[x]=tot; v[tot]=y; remain[tot]=cap;    ++tot; next[tot]=point[y]; point[y]=tot; v[tot]=x; remain[tot]=0;}inline void bfs(int t){    for (int i=1;i<=n;++i)      deep[i]=n;    deep[t]=0;    while (!q.empty()) q.pop();    q.push(t);    while (!q.empty()){        int now=q.front(); q.pop();        for (int i=point[now];i!=-1;i=next[i])          if (deep[v[i]]==n&&remain[i^1]){            deep[v[i]]=deep[now]+1;            q.push(v[i]);          }    }}inline int addflow(int s, int t) {    int ans=inf,now=t;    while (now!=s) {        ans=min(ans, remain[last[now]]);        now=v[last[now] ^ 1];    }    now=t;    while (now != s) {        remain[last[now]]-=ans;        remain[last[now]^1]+=ans;        now=v[last[now]^1];    }    return ans;}inline void isap(int s,int t){    int now=s;    bfs(t);    for (int i=1;i<=n;++i) ++num[deep[i]];    for (int i=1;i<=n;++i) cur[i]=point[i];    //在残量网络中没有源点到汇点的通路     while (deep[s]<n){        //如果到达汇点则进行增广,重新回到源点准备下一轮增广         if (now==t){            maxflow+=addflow(s,t);            now=s;        }        bool has_find=false;        //当前弧优化         for (int i=cur[now];i!=-1;i=next[i]){            int u=v[i];            if (deep[u]+1==deep[now]&&remain[i]){                has_find=true;                cur[now]=i;                last[u]=i;                now=u;                break;            }        }        //没有找到出边,重新进行标号         if (!has_find){            int minn=n-1;            for (int i=point[now];i!=-1;i=next[i])              if (remain[i])                minn=min(minn,deep[v[i]]);            //GAP优化             if (!(--num[deep[now]])) break;            num[deep[now]=minn+1]++;            cur[now]=point[now];            if (now!=s)              now=v[last[now]^1];        }    }}int main(){    tot=-1;    memset(point,-1,sizeof(point));    memset(next,-1,sizeof(next));    scanf("%d%d",&m,&n);    for (int i=1;i<=m;++i){        scanf("%d%d%d",&x,&y,&cap);        add(x,y,cap);    }    isap(1,n);    printf("%d\n",maxflow);}
2 0