网络流之最大流

  因为网上介绍网络流和最大流理论的文章非常的多,也都解释的非常清晰并附带图解。再加上绘图技术不佳,语文表达极差,就只讲解一下程序实现部分。想看理论讲解及算法正确性证明的读者可以上网搜一搜,这里推荐一篇博客,链接如下:
  http://blog.csdn.net/mystery_guest/article/details/51910913
  那么,既然网上的资源那么多,为什么还要写这篇博客呢?一是为了凑数做笔记,二是网上的许多算法模板都十分工程化,变量名贼长,代码量巨大也不容易理解,在博主初学时带来了不小麻烦。于是,本着能水一篇是一篇普度众生的情怀,博主在这里就讲解一下,最大流的几种算法与自认为很简洁的代码。
  1.Ford-Fulkerson(二F)算法
  因为本算法博主并没有学比较简单且时间复杂度极高,基本被算法竞赛淘汰了,故不多作讲解,感兴趣的读者请自行搜索。
  2.Edmonds-karp(EK)算法
  Ek算法最多处理5000条边的网络,时间复杂度仍然较高,近几年基本用不到了,只能作为初学网络流的oier增广路算法和残量网络等网络流基本概念的练手,例题如下:
全流Total Flow(来源:https://www.luogu.org/problemnew/show/2936
这里写图片描述
输入输出样例
输入样例#1:
5
A B 3
B C 3
C D 5
D Z 4
B Z 6
输出样例#1:
3
  反正这个题只是让大家检验一下自己编的算法正确性的,博主直接上代码,本篇博客的重点放在下一个算法,代码如下:

#include<bits/stdc++.h>
using namespace std;
int sq[300][300];//存图
int flow[300];//存流量
int pre[300];//记录增广路路径
int n;
queue<int>dui;//bfs队列
int bfs(int s,int e)//bfs寻找增广路
{
    while(!dui.empty())
    {
        dui.pop();
    }
    for(int i=1;i<=60;i++)
    {
        pre[i]=-1;
    }
    pre[s]=0;
    flow[s]=1e9;
    dui.push(s);
    while(!dui.empty())
    {
        int lo=dui.front();
        dui.pop();
        if(lo==e)
        {
            break;
        }
        for(int i=1;i<=60;i++)
        {
            if(i!=s&&sq[lo][i]>0&&pre[i]==-1)
            {
                pre[i]=lo;//记录上一个点
                flow[i]=min(sq[lo][i],flow[lo]);//寻找增广路上的最小流量
                dui.push(i);
            }
        }
    }
    if(pre[e]==-1)
    {
        return -1;//如果终点没有标记则无法增广,返回负值
    }
    return flow[e];//返回最小流量
}
int maxf(int s,int e)
{
    int in=bfs(s,e),sum=0;
    while(in!=-1)//若存在增广路就一直bfs增广
    {
        int k=e;
        while(k!=s)//从最后一个点往回跳
        {
            int lo=pre[k];
            sq[lo][k]-=in;
            sq[k][lo]+=in;//修改该路径上的流量
            k=lo;
        }
        sum+=in;//记录流量
        in=bfs(s,e);
    }
    return sum;
}
int main()
{
    int a,b,c;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {//普通的建图
        char ch1[2],ch2[2];
        int k,s=0;
        scanf("%s%s%d",ch1,ch2,&c);
        a=ch1[0]-64;
        b=ch2[0]-64;
        sq[a][b]+=c;
    }
    printf("%d",maxf(1,26));
    return 0;
}

  就这样把两个算法水过了,相信大家一定受益匪浅,那么接下来就进入本篇博客的重点。
  3.Dinic算法
  Dinic能够处理大概500000条边的最大流问题,对于不太刁钻的数据,1000000条边也是可以开个O(3)优化过的,在算法竞赛中还是比较实用,代码量也比较小,相比于EK而言只多了30行/300byte(其实应该更小,由于博主的辣鸡格式所以行数较多)。
  依然给出例题,是个纯模板题大家可以交上去检验一下。
  网络最大流(来源:https://www.luogu.org/problemnew/show/3376
这里写图片描述
输入输出样例
输入样例#1:
4 5 4 3
4 2 30
4 3 20
2 3 20
2 1 30
1 3 40
输出样例#1:
50
  Dinic算法的主要优化,就是加了一个bfs分层,将网络分成多个层次,每次dfs寻找增广路时都只搜索下一层,返回的时候修改残量网络里的值。
  定义结构体/数组如下

const int inf=0x3f3f3f3f;
const int maxn=1e6+5;
struct edge{
    int fl,to;//fl记录该边的流量,to为该边的终点
};
edge ed[maxn<<1];//储存边,注意要开两倍大小
int g=0;//记录边的数量,只在加边的时候有用
vector<int>sq[maxn];//储存已该为起点的边的编号

  这个结构体只用维护两个量,相较网上的其他结构体比较简单。
  接下来是加边的函数,代码如下:

void add(int x,int yint a)
{
    ed[g]=(edge){a,y};//相当于将一个fl=a,to=y的edge类型变量放进ed数组
    sq[x].push_back(g++);//将这个边的编号push进去
    ed[g]=(edge){0,x};
    sq[y].push_back(g++);//同上
}

  到目前为止,dinic都十分简单,接下来是bfs分层函数,跟普通的bfs相比只改动了一点,应该也很好懂,上代码,详细讲解见注释:

int d[maxn];
bool bfs(int s,int e)//s起点,e终点
{
    memset(d,0,sizeof(d));
    queue<int>dui;
    dui.push(s);
    d[s]=1;//初始化
    while(!dui.empty())
    {
        int lo=dui.front();
        dui.pop();
        for(int i=sq[lo].size()-1;i>=0;i--)//普通的bfs
        {
            int hh=sq[lo][i];//记录对应边的编号
            int to=ed[hh].to;
            if(!ed[hh].fl||d[to])//如果边的流量为0或已经目标点已经标记,跳过
            {
                continue;
            }
            d[to]=d[lo]+1;
            dui.push(to);//普通的bfs
        }
    }
    return d[e];//返回终点的层数,没有标记过就会返回false,其他都是true
}

  跟普通bfs完全只有一行代码有区别对吧,相信大家依然活力十足,想要继续读完这篇博客(手动滑稽),接下来就是dfs找增广路了。

int dfs(int s,int e,int minn)//s起点,e终点,minn记录增广的值
{
    if(s==e)
    {
        return minn;//到达终点就回溯
    }
    int ans=0;
    for(int i=sq[s].size()-1;i>=0;i--)
    {//常规dfs
        int hh=sq[s][i];
        int fl=ed[hh].fl;
        int to=ed[hh].to;
        if(d[to]!=d[s]+1||!fl)//如果目标点不在下一层或者流为0,跳过
        {
            continue;
        }
        int tmp=dfs(to,e,min(minn-ans,fl));//递归搜索
        if(!tmp)//如果返回值已经为0,直接进入下一个递归,不再回溯
        {
            continue;
        }
        ed[hh].fl-=tmp;
        ed[hh^1].fl+=tmp;//修改边的流
        ans+=tmp;//记录增广的值
    }
    return ans;
}

  Dinic的主要函数都已经讲的差不多了,依然非常的简单,最后来个dinic函数解决战斗

 int dinic(int s,int e)
{
    int ans=0;
    while(bfs(s,e))//如果有终点依然可以到达,就一直增广
    {
        ans+=dfs(s,e,inf);//记录流量
    }
    return ans;
}

  在最最最后,贴上博主自认为简洁的代码,方便大家复制。

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn=1e6+5;
struct edge{
    int fl,to;
};
edge ed [maxn<<1];
vector<int>sq[maxn];
int g=0;
void add(int x,int y,int a)
{
    ed[g]=(edge){a,y};
    sq[x].push_back(g++);
    ed[g]=(edge){0,x};
    sq[y].push_back(g++);
}
int d[maxn];
bool bfs(int s,int e)
{
    memset(d,0,sizeof(d));
    queue<int>dui;
    dui.push(s);
    d[s]=1;
    while(!dui.empty())
    {
        int lo=dui.front();
        dui.pop();
        for(int i=sq[lo].size()-1;i>=0;i--)
        {
            int hh=sq[lo][i];
            int to=ed[hh].to;
            if(!ed[hh].fl||d[to])
            {
                continue;
            }
            d[to]=d[lo]+1;
            dui.push(to);
        }
    }
    return d[e];
}
int dfs(int s,int e,int minn)
{
    if(s==e)
    {
        return minn;
    }
    int ans=0;
    for(int i=sq[s].size()-1;i>=0;i--)
    {
        int hh=sq[s][i];
        int fl=ed[hh].fl;
        int to=ed[hh].to;
        if(d[to]!=d[s]+1||!fl)
        {
            continue;
        }
        int tmp=dfs(to,e,min(minn-ans,fl));
        if(!tmp)
        {
            continue;
        }
        ed[hh].fl-=tmp;
        ed[hh^1].fl+=tmp;
        ans+=tmp;
    }
    return ans;
}
int dinic(int s,int e)
{
    int ans=0;
    while(bfs(s,e))
    {
        ans+=dfs(s,e,inf);
    }
    return ans;
}
int main()
{
    int n,m,s,e;
    scanf("%d%d%d%d",&n,&m,&s,&e);
    int a,b,c;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    printf("%d",dinic(s,e));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShadyPi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值