浅谈最大流
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);//刚开始进起点的量是无限大
}