网络流初步学习之最大流

前一段阵子学了极小的一部分网络流,这里做一些总结,主要还是给自己看的a

最大流:

题干描述:

给出一个网络图,以及其源点和汇点,求出其网络最大流。

输入格式:

第一行包含四个正整数N、M、S、T,分别表示点的个数、有向边的个数、源点序号、汇点序号。

接下来M行每行包含四个正整数ui、vi、wi、fi,表示第i条有向边从ui出发,到达vi,边权为wi(即该边最大流量为wi),单位流量的费用为fi。

输出格式:

一行,包含两个整数,依次为最大流量和在最大流量情况下的最小费用。

开始正题了:

先贴一下代码:

EK:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
const int inf = 1 << 30;
int n,m,s,t;
struct Node
{
    int v;
    int val;
    int next;
}node[200866];
struct Pre
{
    int v;  //该点的前一个点
    int edge;  //与该点相连的边
}pre[100866];
int top = 1,head[100866];  //top必须从一个奇数开始
inline void addedge(int u,int v,int val)
{
    node[++top].v = v;
    node[top].val = val;
    node[top].next = head[u];
    head[u] = top;
}
inline int read()
{
    int x = 0;
    char ch = getchar();
    while(ch > '9' || ch < '0')
    ch = getchar();
    while(ch >= '0' && ch <= '9')
    {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x;
}
int inque[100866];//点是访问过里 
inline bool bfs()  //是否有增广路
{
    queue<int>q;
    memset(inque,0,sizeof(inque));
    memset(pre,-1,sizeof(pre));
    inque[s] = 1;
    q.push(s);
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        for(int i=head[u];i;i=node[i].next)
        {
            int d = node[i].v;
            if(!inque[d] && node[i].val)
            {
                pre[d].v = u;
                pre[d].edge = i;
                if(d == t)
                return 1;
                inque[d] = 1;
                q.push(d);
            }
        }
    }
    return 0;
}
int EK()
{
    int ans = 0;
    while(bfs())
    {
        int minn = inf;
        for(int i=t;i!=s;i=pre[i].v)
            minn=min(minn,node[pre[i].edge].val);//增广路上最小的边的权值 
        for(int i=t;i!=s;i=pre[i].v)
        {
            node[pre[i].edge].val -= minn;
            node[pre[i].edge ^ 1].val += minn;
            //反向的边的编号是正向边的编号^1
        }
        ans += minn;
    }
    return ans;
}
int main()
{
    n = read();
    m = read();
    s = read();
    t = read();
    int u,v,w;
    for(int i=1;i<=m;i++)
    {
        u = read();
        v = read();
        w = read();
        addedge(u,v,w);
        addedge(v,u,0);
    }
    printf("%d",EK());
    return 0;
}

Dinic:

按(+1)找反向边,个人认为不大靠谱

#include<cstdio>
#include<queue>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#define MAXN 20086
using namespace std;
const int inf = 1e9;
int n,m,s,t;
int cnt,max_flow;
int dep[MAXN];  //记录每一个节点的深度 
int head[MAXN];  //存图 
int cur[MAXN];  //记录路径的 
queue<int> q;
struct Node
{
    int len;
    int next;
    int to;
}edge[MAXN * 10 << 1];
inline void add_edge(int u,int v,int w)  //加边建图 
{
    edge[++cnt].to = v;
    edge[cnt].len = w;
    edge[cnt].next = head[u];
    head[u] = cnt;
}
inline bool bfs(int s,int t)  //判断是不是可以跑 
{
    memset(dep,0x7f,sizeof(dep));
    while(!q.empty())
    q.pop();  
    for(int i=1;i<=n;i++)
    cur[i] = head[i];  //初始化 
    
    dep[s] = 0;  //起点开始 
    q.push(s);
    
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        for(int i=head[u];i;i=edge[i].next)
        {
            int v = edge[i].to;
            if(dep[v] > inf && edge[i].len)  //正向边,可以跑 
            {
                dep[v] = dep[u] + 1;
                q.push(v);
            }
        }
    }
    if(dep[t] < inf)
    return true;
    else
    return false;
}
inline int dfs(int now,int t,int limit)  //limit就是一个限制 
                                         //dfs的优点:在一次增广的过程中,寻找多条增广的路径 
{
    if(!limit || now == t)
    return limit;
    int flow = 0;
    int f;
    
    for(int i=head[now];i;i=edge[i].next)
    {
        cur[now] = i;  //i是边的编号,更改流道的路径 
        int v = edge[i].to;
        if(dep[v] == dep[now] + 1 && (f = dfs(v,t,min(limit,edge[i].len))))
        {
            flow += f;
            limit -= f;
            edge[i].len -= f;
            edge[i + 1].len += f;
            if(!limit)
            break;
        }
    }
    return flow;
}
void Dinic(int s,int t)
{
    while(bfs(s,t))
    max_flow += dfs(s,t,inf);
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w); add(v,u,0); 
    }
    Dinic(s,t);
    printf("%d",max_flow);
    return 0;
}

按(^1)找反向边,注意cnt初始化为-1

#include<cstdio>
#include<queue>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#define MAXN 20086
using namespace std;
const int inf = 1e9;
int n,m,s,t;
int u,v,w;
int cnt = -1;  //为了 ^ 的找反向边做准备 
int max_flow;
int dep[MAXN];  //记录每一个节点的深度 
int head[MAXN];  //存图 
int cur[MAXN];  //记录路径的 
queue<int> q;
struct Node
{
    int len;
    int next;
    int to;
}edge[MAXN * 10 << 1];
inline void add_edge(int u,int v,int w)  //加边建图 
{
    edge[++cnt].to = v;
    edge[cnt].len = w;
    edge[cnt].next = head[u];
    head[u] = cnt;
}
inline bool bfs(int s,int t)  //判断是不是可以跑 
{
    memset(dep,0x7f,sizeof(dep));
    while(!q.empty())
    q.pop();
    for(int i=1;i<=n;i++)
    cur[i] = head[i];  //初始化 
    
    dep[s] = 0;  //起点开始 
    q.push(s);
    
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        for(int i=head[u];i;i=edge[i].next)
        {
            int v = edge[i].to;
            if(dep[v] > inf && edge[i].len)  //正向边,可以跑 
            {
                dep[v] = dep[u] + 1;
                q.push(v);
            }
        }
    }
    if(dep[t] < inf)
    return true;
    else
    return false;
}
inline int dfs(int now,int t,int limit)  //limit就是一个限制 
                                         //dfs的优点:在一次增广的过程中,寻找多条增广的路径 
{
    if(!limit || now == t)
    return limit;
    int flow = 0;
    int f;
    
    for(int i=head[now];i;i=edge[i].next)
    {
        cur[now] = i;  //i是边的编号,更改流道的路径 
        int v = edge[i].to;
        if(dep[v] == dep[now] + 1 && (f = dfs(v,t,min(limit,edge[i].len))))
        {
            flow += f;
            limit -= f;
            edge[i].len -= f;
            edge[i ^ 1].len += f;
            if(!limit)
            break;
        }
    }
    return flow;
}
void Dinic(int s,int t)
{
    while(bfs(s,t))
    max_flow += dfs(s,t,inf);
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        add_edge(u,v,w);
        add_edge(v,u,0); 
    }
    Dinic(s,t);
    printf("%d",max_flow);
    return 0;
}

 原理

对于网络流,我们粗暴地每一个节点的SPFA显然是较劣的

所以我们需要一种算法来得到最大流量

首先介绍一下EK,同时它也是后面Dinic的基础

EK的着眼点就在于我们知道跑SPFA是显然不行的,那么我们就可以针对这一点进行优化

优化的措施就是建立反向边

反向边的含义就是说给了一次反悔的机会,让我们可以退回到当前状态之前的状态

这也是EK算法的核心,既然我们不能直接跑,那么我们就可以在建图的过程中针对于每一条边都建立一条它所对应的反向边

我们就可以通过反向边进行回溯,从而得到通过每一个节点的最优解法(同时,这个的鲁棒性可以帮助我们解决一些Dinic解决不了的问题)

EK的跑法就是在SPFA的过程中将每一种状态的最优结果找出来,从而得到一个这些结果里面最大的收益

以上黄线标注的地方就是Dinic针对于EK所改进的地方

因为在跑EK的时候做了大量的"无用功",那么我们可不可以减少BFS的次数呢?

Dinic就通过DFS将BFS的次数缩短了

这里我们把BFS更改为bool类型,来判断可不可以向下流

初始化dep[T] = 0x7f

然后判断假如可以流的话,那么dep[T]肯定是小于这个数值的(哪有图0x7f深度的)

然后我们可以知道这个流可不可以向下流动

然后我们就可以在bfs可行的基础上进行dfs了

dfs的优点:在一次增广的过程中,寻找多条增广的路径

这样肯定是优于全跑一遍的

完结

猜你喜欢

转载自www.cnblogs.com/lyp-Bird/p/10963567.html