网络最大流(EK)

以前在oi中见到网络流的题都是直接跳过,由于本蒟蒻的理解能力太弱,导致网络流的学习不断推迟甚至被安排在了tarjan之后,原本计划于学习完最短路后就来学网络流的想法也随之破灭,在看完众多大佬

的博客后,我发现我不怎么能看懂(因为我自己太菜了),所以特来写一篇整理一下自己所学到的.

常见的网络流算法根据优化程度有FF<EK<Dinic<ISAP,由于后两种算法比较复杂,我至今也没有很好的理解,今天只要是我自己的一些对EK的理解.

首先需要了解一下什么是网络最大流:

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

  • 定义:带权的有向图G=(V,E),满足以下条件,则称为网络流图(flow network):

    1. 仅有一个入度为0的顶点s,称s为源点
    2. 仅有一个出度为0的顶点t,称t为汇点
    3. 每条边的权值都为非负数,称为该边的容量,记作c(i,j)。

    弧的流量:通过容量网络G中每条弧< u,v>,上的实际流量(简称流量),记为f(u,v);

     网络流具有以下性质:

                    1.边所容纳的流量<=该边的最大容量.

                    2.反对称性,即f[u][v]=-f[v][u].

                    3.从源点流出的流量总是等于汇点汇聚的流量,对于其他的普通点来说,入度来的流量和等于出度出去的流量和.

最大网络流:网络流量图G中,最大的可行流称为网络最大流

我们定义:
源点:只有流出去的点
汇点:只有流进来的点
流量:一条边上流过的流量
容量:一条边上可供流过的最大流量
残量:一条边上的容量-流量

增广路:找到一条从源点到汇点的路径,使得路径上任意一条边的残量>0(注意是大于而不是大于等于,这意味着这条边还可以分配流量),这条路径便称为增广路

 

EK:不断找到一条起点到终点的路径,若有,找出这条路径上每一条边的最小值,然后将这条路上的每一条正向边减去这条路的流量,再在这条路上的每一条反向边加上这条边的流量,再在剩下的图上寻找新的路径,使总流量增加。然后形成新的图,再寻找新路径…..直到找不到从起点到终点的路径为止,最大流就算出来了。

#include<bits/stdc++.h>
using namespace std;
int st[1000010],vis[1000010],n,m,s,flow[1000010],maxflow,t,x,y,z,tot=-1,pre[1000010],q[1000010];
struct node
{
    int from,to,val,last;
/*
from记录起点
to记录重点
val记录这条边的流量
last记录起点和当前这条边的起点一样的边的序号 
*/
}e[1000001];
int mn(int x,int y)
{
    if(x>y)
        return y;
    return x;
}
void add(int f,int t,int v)
{//链式前向星 
    tot++;//当前这条边的序号 
    e[tot].from=f; 
    e[tot].to=t; 
    e[tot].val=v; 
    e[tot].last=st[f]; 
    st[f]=tot; 
    return ; 
} 
bool bfs(int s,int m)//寻找从起点到终点的路径 
{
    int t=0,h=1;
    for(int i=1;i<=n;i++)
    {
        vis[i]=-1;//一开始把所有的点(包括起点和终点)标志为没有走过 
        pre[i]=-1;//记录上一条到达i点的边的起点的编号 
    }
    t++;
    q[t]=s;//将起点入队
    vis[s]=1; 
    flow[s]=2147483647;
    while(t>=h)
    {
        int u=q[h];//拿出队首 
        vis[u]=0;//当前的队首标志为不在队列里 
        h++;//踢出队首 
        for(int i=st[u];i!=0;i=e[i].last)
        {
            int v=e[i].to;
            if(e[i].val!=0 && vis[v]==-1)//如果当前遍历到的点不在队列里(避免重复入队)就入队 
            {
                flow[v]=mn(flow[u],e[i].val);//更新这条路径的流量(比较,求出最小值) 
                pre[v]=i;//编号为i的这条边到达了v点,更新pre[v] 
                t++;//入队 
                q[t]=v; //
                vis[v]=u; 
            } 
        } 
    } 
    if(vis[m]!=-1)//从当前点s可以走到点m 
        return true; 
    return false;//如果运行到了这里,说明再也找不到从起点到终点的路了 
} 
void update(int s,int t)//一个回溯过程 
{//如果能进这个函数,说明从当前点s可以走到点t 
    int now=t;//从终点往回查找 
    while(now!=s)//如果当前回溯到的点不是起点 
    {//继续找
        int i=pre[now];
        e[i].val-=flow[t];//正向边加上这条边的流量 
        e[i^1].val+=flow[t];//建反向边,减去这条边的流量 
        now=e[i].from;//更新当前回溯到的点 
    }
    maxflow+=flow[t];//加上当前这条边的流量 
}
void EK(int s,int t)
{
    maxflow=0;//重置最大流 
    while(bfs(s,t)==true)//如果当前找得到一条从起点到终点的路 
        update(s,t);//加上这条路的流量 
}
int main()
{
    scanf("%d %d %d %d",&n,&m,&s,&t);
    for(int i=1;i<=m;i++)
    {
        scanf("%d %d %d",&x,&y,&z);
        add(x,y,z);//建正向边 
        add(y,x,0);//建反向边 
    }
    EK(s,t);//求最大流 
    printf("%d",maxflow);//输出 
    return 0;
}

 

posted @ 2020-02-11 22:01  remarkableboy  阅读(681)  评论(0编辑  收藏  举报