浅谈最大流

sap

思路

本蒟蒻太蒟,只会这个算法
首先,我们来几个前置知识。

  • 增广路径就是指从起点到终点的路径。
  • 我们把一个节点离终点的最短距离称为距离标号。

因为增广路径意味着可以将答案加大,所以说我们每次就找增广路径就行了。
但是,你所找的增广路径不一定是最优的,所以说我们就要增加反悔机制。

反悔机制就是我们每次在将水流过去后,我们在加一条管道,这条管道就是让我们流过去的水可以流回来,这就是一个反悔机制,也就是下面的代码。

a[i][j]-=delta,a[j][i]+=delta;//delta就是你流过去多少水

但是我们要是每次都随便找增广路径的话,那可能会被卡掉。
所以说我们就要想个办法,于是我们就可以每次都找最短的增广路径,就可以用到距离标号了。

每次从起点出发,看一下跟自己相连的节点的距离编号如果是自己的距离编号减一,就往那里走。

if(d[now]==d[to]+1)
{
    枚举to
}

但是我们在找到一个增广路径的时候,d值可能会改变,怎么办呢?
我们就来看看从now能不能走到某一个节点,要是不能,我们就将他的距离编号变成与他相连的最小的距离编号值加一。(也就可以不用初始化距离编号,毕竟你就把刚开始的距离编号当成不合法的做就可以了)
还有一个优化,叫做GAP优化,就是如果d值有断层,那么就可以直接说明你现在找不出增广路径了。
我们还可以在增广一个节点的时候把他的分岔一次性全部增广了,不必分为几条增广路径增广。

代码(有注释)

/*
flow 就是你的答案,找到增广路径可以将答案增大,每次找增广路径增大答案就可以了。
augco 从i为起点的最大增广容量
augc 记录剩下的需要增广的量
mind 与你相连的最小的d值
*/
int aug(int i,int augco)
{
    if(i==m) return augco;//如果到了终点,就直接返回你增广的量
    int augc=augco,mind=m-1,delta;
    for(int j=1;j<=m;j++)
        if(a[i][j]>0)
        {
            if(d[i]==d[j]+1)//如果i能到j
            {
                delta=aug(j,min(augc,a[i][j]));//min中的意思就是你原本能增广的量和这条管子能增广的量的最小值
                a[i][j]-=delta,a[j][i]+=delta,augc-=delta;
                if(d[1]>=m) return augco-augc;//如果距离标号的大小大于所有的点数,就说明没有增广路径了
                if(augc==0) break;
            }
            if(mind>d[j]) mind=d[j];//找与自己相连的距离标号的最小值
        }
    if(augco==augc)//如果你不能从now走到任何一个点,就更新这个now值
    {
        vd[d[i]]--;
        if(vd[d[i]]==0) d[1]=m;//如果断层了,那么就可以结束。因为之改变了d[i]的值,就可以只判断d[i]
        d[i]=mind+1,vd[d[i]]++;
    }
    return augco-augc;//向上一层返回你在这个节点所能增广的量
}
void sap()
{
    memset(d,0,sizeof(d)),memset(vd,0,sizeof(vd));
    vd[0]=m;
    while(d[1]<m)
        flow+=aug(1,99999999);//刚开始进起点的量是无限大
}
posted @ 2021-11-16 16:13  Konjac_HZX  阅读(39)  评论(0编辑  收藏  举报