网络流概念+EdmondKarp算法+Dinic(Dinitz)

网络流问题在实际解决题目中有很多用处

有时可与二分图匹配相搭配(然而我还没有学二分图匹配2333)

所以我还是决定学一下网络流

于是便有了这篇博客

废话不多说

先介绍下概念

关于网络流的基本概念

图,边,点

设边(Edge)的集合为E

点(vertex)的集合为V

图为G

则G=(V,E)

(color{pink}{S指源点(起点),T指汇点(终点)})

(color{pink}{注意,这里E和V是集合,而G是一个二元组})

(color{pink}{二元组:数据结构的一种(不是指信息的,也许信息也有 雾),二元组(D,R),D为数据元素的有限集,R为D的关系之间的有限集})

(color{pink}{明显这里D指V,R指E})

容量网络

G(V,E)只不过要求

  • 有向

  • 相邻两点间的最大边大于0(c(u,v)>0)

则称G为容量网络

弧的流量

话说 我不懂为什么都叫它弧不叫边的2333

即实际流量用f(u,v)表示

网络流

即集合 f = { u(in)V,v(in)V-{u}|f(u, v) }

可行流

  • 0 ≤ f(u,v) ≤ c(u,v)

  • 平衡条件除源点S和汇点T外流入流量总和=流出流量总和

增广路

即一条从S到T的路径且路径上每条边残留容量都为正(c(u,v)-f(u,v)>0)

增广

将S到T的一条增广路拿出来,设其中的最小边权(残留容量)为minl

将该路途上的每边 -= minl

将minl累加至ans中

以上 为前置概念

算法

题目

接下来的题目以P3376 【模板】网络最大流为例子

EdmondKarp

该算法时间复杂度O(V(E^2))

思路不断找增广路进行增广,当找不到增广路时,即此时的ans就为网络最大流

找增广路

inline bool Bfs()
{
    bool vis[MAXN] = {};
    memset(pre,0,sizeof(pre));
    queue<int> q;
    q.push(s);
    vis[s] = 1;
    while(!q.empty())
    {
        int u = q.front();
        if(u == t) return 1;
        q.pop();
        for(int i = fire[u];i;i = Edge[i].next)
        {
            int v = Edge[i].v;
            if(!vis[v]&&Edge[i].w)
            {
                vis[v] = 1;
                pre[v].v = u;
                pre[v].Edge = i;
                //pre用来记录路径
                if(v == t) return 1;
                q.push(v);
            }
        }
    }
    return 0;
}

EdmondKarp

inline int EdmondKarp()
{
    int ans = 0;
    while(Bfs())
    {
        int i,minl = inf;
        for(i = t;i != s;i = pre[i].v)
             minl = min(minl,Edge[pre[i].Edge].w);
        //找最小值
        for(i = t;i != s;i = pre[i].v)
        {
            Edge[pre[i].Edge].w -= minl;
            //Edge[pre[i].Edge ^ 1].w += minl;
        }
        //将该路途上的每边 -= minl
        ans += minl;
        //将minl累加至ans中
    }
    return ans;
}

然后这就是大概思路

(color{pink}{但是})

这样会错

假设上面每条边容量为1

如果找到的第一条增广路为1-2-3-4

那么

再也找不到增广路

此时ans 为 1

但显然ans为2

(color{pink}{WA})

原因很显然这样随便走很明显不对啊 又没什么定理保证正确

不过 EdmondKarp还有一点

我们要给程序一个(color{pink}{反悔})的机会

口技·习传

不玩梗了

建双向边, 将该路途上的每边 -= minl的时候同时将该路途上的每边的反边 += minl

即加上上面被我注释掉的代码

建双向边后

蓝边权值1,红边权值0,绿边权值1

第二次就可以找到1-3-2-4了

我们用的是链式前向星

为方便我们让一条边与她的反边相邻

即 使 边id^1=反边id且反边id^1=边id

即 min(边id,反边id) + 1 = max(边id,反边id)

所以第一条边id必须为偶数

否则如果第一条边从奇数开始 她的

反边id=边id+1!=边id^1

代码

#include <queue>
#include <cstdio>
#include <climits>
#include <cstring>
#include <iostream>
using namespace std;

const int MAXM = 200010,MAXN = 10010,inf = INT_MAX;
struct Node
{
    int v,w,next;
    Node(){v = w = next=0;}
}Edge[MAXM];
struct Node2
{
    int v,Edge;
}pre[MAXN];
int cnt,fire[MAXN],s,t;
inline void AddEdge(int u,int v,int w)
{
    Edge[++cnt].v = v;
    Edge[cnt].w = w;
    Edge[cnt].next = fire[u];
    fire[u] = cnt;
}
inline void Build(int u,int v,int w)
{
    AddEdge(u,v,w);
    AddEdge(v,u,0);
    //反边权值为0
}
inline bool Bfs()
{
    bool vis[MAXN] = {};
    memset(pre,0,sizeof(pre));
    queue<int> q;
    q.push(s);
    vis[s] = 1;
    while(!q.empty())
    {
        int u = q.front();
        if(u == t) return 1;
        q.pop();
        for(int i = fire[u];i;i = Edge[i].next)
        {
            int v = Edge[i].v;
            if(!vis[v]&&Edge[i].w)
            {
                vis[v] = 1;
                pre[v].v = u;
                pre[v].Edge = i;
                if(v == t) return 1;
                q.push(v);
            }
        }
    }
    return 0;
}
inline int EdmondKarp()
{
    int ans = 0;
    while(Bfs())
    {
        int i,minl = inf;
        for(i = t;i != s;i = pre[i].v)
             minl = min(minl,Edge[pre[i].Edge].w);
        for(i = t;i != s;i = pre[i].v)
        {
            Edge[pre[i].Edge].w -= minl;
            Edge[pre[i].Edge ^ 1].w += minl;
        }
        ans += minl;
    }
    return ans;
}
int main()
{
    cnt = -1;
    这里设为-1,但Build里+了1
    所以实际上第一条边从零(偶数)开始的(~~魔法书~~/~~异世界生活~~)
    int i,n,m;
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(i = 1;i <= m;i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        Build(u,v,w);
    }
    int ans = EdmondKarp();
    printf("%d",ans);
    return 0;
}

这样就可以AC这道题P3376 【模板】网络最大流

(color{pink}{但是})

这样的代码还有bug 所以我说洛谷数据水啊


我的链式前向星是这样写的

明显条件是当i等于0时退出

但 之前cnt初值为-1

第一条边id为0

所以当i等于0的时候也可能指的第一条边

订正 将cnt改为 1
for(int i = fire[u];i;i = Edge[i].next)
        {
            int v = Edge[i].v;
            if(!vis[v]&&Edge[i].w)
            {
                vis[v] = 1;
                pre[v].v = u;
                pre[v].Edge = i;
                if(v == t) return 1;
                q.push(v);
            }
        }
#include <queue>
#include <cstdio>
#include <climits>
#include <cstring>
#include <iostream>
using namespace std;

const int MAXM = 200010,MAXN = 10010,inf = INT_MAX;
struct Node
{
    int v,w,next;
    Node(){v = w = next=0;}
}Edge[MAXM];
struct Node2
{
    int v,Edge;
}pre[MAXN];
int cnt,fire[MAXN],s,t;
inline void AddEdge(int u,int v,int w)
{
    Edge[++cnt].v = v;
    Edge[cnt].w = w;
    Edge[cnt].next = fire[u];
    fire[u] = cnt;
}
inline void Build(int u,int v,int w)
{
    AddEdge(u,v,w);
    AddEdge(v,u,0);
}
inline bool Bfs()
{
    bool vis[MAXN] = {};
    memset(pre,0,sizeof(pre));
    queue<int> q;
    q.push(s);
    vis[s] = 1;
    while(!q.empty())
    {
        int u = q.front();
        if(u == t) return 1;
        q.pop();
        for(int i = fire[u];i;i = Edge[i].next)
        {
            int v = Edge[i].v;
            if(!vis[v]&&Edge[i].w)
            {
                vis[v] = 1;
                pre[v].v = u;
                pre[v].Edge = i;
                if(v == t) return 1;
                q.push(v);
            }
        }
    }
    return 0;
}
inline int EdmondKarp()
{
    int ans = 0;
    while(Bfs())
    {
        int i,minl = inf;
        for(i = t;i != s;i = pre[i].v)
             minl = min(minl,Edge[pre[i].Edge].w);
        for(i = t;i != s;i = pre[i].v)
        {
            Edge[pre[i].Edge].w -= minl;
            Edge[pre[i].Edge ^ 1].w += minl;
        }
        ans += minl;
    }
    return ans;
}
int main()
{
    cnt = 1;
    int i,n,m;
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(i = 1;i <= m;i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        Build(u,v,w);
    }
    int ans = EdmondKarp();
    printf("%d",ans);
    return 0;
}

这样才没问题T_T

Dinic(Dinitz)

作者叫Dinitz

后来某大佬(雾)教课时念成了Dinic

于是大家都叫这个算法为Dinic算法了2333

这个算法大概就是EdmondKarp的升级版

理论上快很多 但最慢会退化为(O(VE^2))

思路就是 考虑到EdmondKarp每次都重新一遍Bfs找增广路

我们能否在一次或几次搜索中找到很多条增广路呢?

这就是Dinic算法(连我也这样叫 恩 作者的真名不用管了)的核心处理之处了

具体实现

引入一个层次网络的概念

其实就是给予(forall) v (in)V 一个值 (depth_i)

记录从源点到她最少需要经过几条还活着的边(边权为0的已经死了)

这个操作一遍Bfs()搞定

然后找增广路就用Dfs()来找

(Bfs)

inline bool Bfs()
{
    memset(depth,-1,sizeof(depth));
    queue<int> q;
    q.push(s);
    depth[s] = 1;
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        for(int i = fire[u];i;i = Edge[i].next)
        {
            int v = Edge[i].v;
            if(depth[v] == -1&&Edge[i].w)
            {
                depth[v] = depth[u] + 1;
                q.push(v);
            }
        }
    }
    return (depth[t] != -1);
}

Dfs

inline int Dfs(int u,int minl)
{
    if(u == t) return minl;
    int i,delta1 = 0;
    for(i = head[u];i;i = Edge[i].next)
    {
        int v = Edge[i].v,w = Edge[i].w;
        head[u] = i;
        不断更新head[u]
        直接用fire[u]也没错 但在同一次Dfs已经走过的最后的一条边失去价值
        减少时间复杂度
        if(depth[v] == depth[u] + 1&&w)
        {
            int delta = Dfs(v,min(minl,w));
            minl -= delta;
            delta1 += delta;
            Edge[i].w -= delta;
            Edge[i ^ 1].w += delta;
            if(!minl) break;
        }
    }
    if(!delta1) depth[u] = -1;
    return delta1;
}

Dinic(Dinitz)

inline int Dinic()
{
    int ans = 0;
    while(Bfs())
    {
        for(int i = 1;i <= n;i++) head[i] = fire[i];
        因为head在Dfs中进行了更改,但这对另外一次Dfs不造成影响
        因为另外一次Dfs前由再进行了一次Bfs改变了depth
        所以这里得把值赋回去
        ans += Dfs(s,INT_MAX);
    }
    return ans;
}

完整代码

#include <queue>
#include <cstdio>
#include <climits>
#include <cstring>
#include <iostream>
using namespace std;

const int MAXM = 200010,MAXN = 10010,inf = INT_MAX;
struct Node
{
    int v,w,next;
    Node(){v = w = next=0;}
}Edge[MAXM];
int cnt,fire[MAXN],head[MAXN],s,t,depth[MAXN],n;
inline void AddEdge(int u,int v,int w)
{
    Edge[++cnt].v = v;
    Edge[cnt].w = w;
    Edge[cnt].next = fire[u];
    fire[u] = cnt;
}
inline void Build(int u,int v,int w)
{
    AddEdge(u,v,w);
    AddEdge(v,u,0);
}
inline bool Bfs()
{
    memset(depth,-1,sizeof(depth));
    queue<int> q;
    q.push(s);
    depth[s] = 1;
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        //printf("%d:",u);
        for(int i = fire[u];i;i = Edge[i].next)
        {
            int v = Edge[i].v;
            //printf("[%d %d %d] ",u,i,v);
            if(depth[v] == -1&&Edge[i].w)
            {
                depth[v] = depth[u] + 1;
                q.push(v);
            }
        }
        //putchar('
');
    }
    return (depth[t] != -1);
}
inline int Dfs(int u,int minl)
{
    if(u == t) return minl;
    int i,delta1 = 0;
    for(i = head[u];i;i = Edge[i].next)
    {
        int v = Edge[i].v,w = Edge[i].w;
        head[u] = i;
        if(depth[v] == depth[u] + 1&&w)
        {
            int delta = Dfs(v,min(minl,w));
            minl -= delta;
            delta1 += delta;
            Edge[i].w -= delta;
            Edge[i ^ 1].w += delta;
            if(!minl) break;
        }
    }
    if(!delta1) depth[u] = -1;
    return delta1;
}
inline int Dinic()
{
    int ans = 0;
    while(Bfs())
    { 
        for(int i = 1;i <= n;i++) head[i] = fire[i];
        ans += Dfs(s,INT_MAX);
    }
    return ans;
}
int main()
{
    cnt = 1;
    int i,m;
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(i = 1;i <= m;i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        Build(u,v,w);
    }
    int ans = Dinic();
    printf("%d",ans);
    return 0;
}

原文地址:https://www.cnblogs.com/resftlmuttmotw/p/11420924.html