首先引入几个新名词:

1、距离标号:

所谓距离标号 ,就是某个点到汇点的最少的弧的数量(即边权值为1时某个点到汇点的最短路径长度)。

设点i的标号为level[i],那么如果将满足level[i]=level[j]+1的弧(i,j)叫做允许弧 ,且增广时只走允许弧

2、断层(本算法的Gap优化思想):

gap[i]数组表示距离标号为i的点有多少个,如果到某一点没有符合距离标号的允许弧,那么需要修改距离标号来找到增广路; 如果重标号使得gap数组中原标号数目变为0,则算法结束。

SAP算法框架:

1、初始化;

2、不断沿着可行弧找增广路。可行弧的定义为{( i , j ) , level[i]==level[j]+1};

3、当前节点遍历完以后,为了保证下次再来的时候有路可走,重新标号当前距离,level[i]=min(level[j]+1)

该算法最重要的就是gap常数优化了。

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1532

题目大意:

就是由于下大雨的时候约翰的农场就会被雨水给淹没,无奈下约翰不得不修建水沟,而且是网络水沟,并且聪明的约翰还控制了水的流速,本题就是让你求出最大流速,无疑要运用到求最大流了。题中N为水沟数,M为水沟的顶点,接下来Si,Ei,Ci分别是水沟的起点,终点以及其容量。求源点1到终点M的最大流速。注意重边

题大意:给出边数N,点数M,每条边都是单向的,问从1点到M的最大流是多少。

EK算法模板

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib> using namespace std; int u,v,dist,a[205][205],flow[205],pre[205],q[205],n,m; int bfs(int s,int des)
{
memset(q,0,sizeof(q));
memset(pre,0,sizeof(pre));
int h=0,t=0;
q[h++]=s; flow[s]=0x7fffffff;
pre[s]=s; flow[des]=0;
while (h!=t)
{
int u=q[t];
if (u==des) break;
for (int v=1;v<=m;v++) if (a[u][v]>0 && !pre[v])
{
q[h++]=v; pre[v]=u;
flow[v]=min(flow[u],a[u][v]);
}
t++;
}
return flow[des];
} int maxflow(int s,int des)
{
int t,ans=0;
while(t=bfs(s,des),t!=0)
{
for (int i=des;i!=s;i=pre[i])
{
a[pre[i]][i]-=t;
a[i][pre[i]]+=t;
}
ans+=t;
}
return ans;
} int main()
{
while (scanf("%d%d",&n,&m)!=EOF)
{
memset(a,0,sizeof(a));
for (int i=1;i<=n;i++)
{
scanf("%d%d%d",&u,&v,&dist);
a[u][v]+=dist;
}
int ans=maxflow(1,m);
printf("%d\n",ans);
}
return 0;
}

SAP算法分析



EK的,最经典,也是最白痴的算法……(不过有些时候正经很好用呢~)

昨天研究了一下神奇的SAP算法,今天又拿他做了一些网络流的题目,发现确实优化效果极其明显!

简单总结一下:

首先我么先回顾一下EK(这个不会的可以看namiheike写的EK的详解,地址:http://www.oibh.org/bbs/thread-29333-1-1.html)。

EK的思想就是每一次都用一个BFS来找到一条增广路,所以说我们就会发现他的复杂度是:O(V*E^2)。所以说我们找到的不一定就是最优的。

 EK详细讲解及优化分析

求最大流有一种经典的算法,就是每次找增广路时用BFS找,保证找到的增广路是弧数最少的,也就是所谓的Edmonds-Karp算法。可以证明的是在使用最短路增广时增广过程不超过V*E次,每次BFS的时间都是O(E),所以Edmonds-Karp的时间复杂度就是O(V*E^2)。

如果能让每次寻找增广路时的时间复杂度降下来,那么就能提高算法效率了,使用距离标号的最短增广路算法就是这样的。所谓距离标号,就是某个点到汇点的最少的弧的数量(另外一种距离标号是从源点到该点的最少的弧的数量,本质上没什么区别)。

设点i的标号为D[i],那么如果将满足D[i]=D[j]+1的弧(i,j)叫做允许弧,且增广时只走允许弧,那么就可以达到“怎么走都是最短路”的效果。每个点的初始标号可以在一开始用一次从汇点沿所有反向边的BFS求出,实践中可以初始设全部点的距离标号为0,问题就是如何在增广过程中维护这个距离标号。

维护距离标号的方法是这样的:当找增广路过程中发现某点出发没有允许弧时,将这个点的距离标号设为由它出发的所有弧的终点的距离标号的最小值加一。

这种维护距离标号的方法的正确性我就不证了。由于距离标号的存在,由于“怎么走都是最短路”,所以就可以采用DFS找增广路,用一个栈保存当前路径的弧即可。当某个点的距离标号被改变时,栈中指向它的那条弧肯定已经不是允许弧了,所以就让它出栈,并继续用栈顶的弧的端点增广。

为了使每次找增广路的时间变成均摊O(V),还有一个重要的优化是对于每个点保存“当前弧”:初始时当前弧是邻接表的第一条弧;在邻接表中查找时从当前弧开始查找,找到了一条允许弧,就把这条弧设为当前弧;改变距离标号时,把当前弧重新设为邻接表的第一条弧,还有一种在常数上有所优化的写法是改变距离标号时把当前弧设为那条提供了最小标号的弧。当前弧的写法之所以正确就在于任何时候我们都能保证在邻接表中当前弧的前面肯定不存在允许弧。

优化2:

还有一个常数优化是在每次找到路径并增广完毕之后不要将路径中所有的顶点退栈,而是只将瓶颈边以及之后的边退栈,这是借鉴了Dinic算法的思想。注意任何时候待增广的“当前点”都应该是栈顶的点的终点。这的确只是一个常数优化,由于当前边结构的存在,我们肯定可以在O(n)的时间内复原路径中瓶颈边之前的所有边。

优化小结:

1.邻接表优化:

如果顶点多的话,往往N^2存不下,这时候就要存边:

存每条边的出发点,终止点和价值,然后排序一下,再记录每个出发点的位置。以后要调用从出发点出发的边时候,只需要从记录的位置开始找即可(其实可以用链表)。优点是时间加快空间节省,缺点是编程复杂度将变大,所以在题目允许的情况下,建议使用邻接矩阵。

2.GAP优化:

如果一次重标号时,出现距离断层,则可以证明ST无可行流,此时则可以直接退出算法。

3.当前弧优化:

为了使每次找增广路的时间变成均摊O(V),还有一个重要的优化是对于每个点保存“当前弧”:初始时当前弧是邻接表的第一条弧;在邻接表中查找时从当前弧开始查找,找到了一条允许弧,就把这条弧设为当前弧;改变距离标号时,把当前弧重新设为邻接表的第一条弧。

学过之后又看了算法速度的比较,发现如果写好的话SAP的速度不会输给HLPP。

SAP实现代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAXN 222
#define inf 100000000+1000
int map[MAXN][MAXN];//存图
int pre[MAXN];//记录当前点的前驱
int level[MAXN];//记录距离标号
int gap[MAXN];//gap常数优化
int NV,NE; //入口参数vs源点,vt汇点
int SAP(int vs,int vt)
{
memset(pre,-1,sizeof(pre));
memset(level,0,sizeof(level));
memset(gap,0,sizeof(gap));
gap[0]=vt;
int v,u=pre[vs]=vs,maxflow=0,aug=inf; while(level[vs]<vt)
{
//寻找可行弧
for(v=1;v<=vt;v++)
{
if(map[u][v]>0&&level[u]==level[v]+1){
break;
}
}
if(v<=vt)
{
pre[v]=u;
u=v;
if(v==vt)
{
int neck=0;
aug=inf;
//寻找当前找到的一条路径上的最大流 , (瓶颈边)
for(int i=v;i!=vs;i=pre[i])
{
if(aug>map[pre[i]][i])
{
aug=map[pre[i]][i];
neck=i;
}
}
maxflow+=aug;
//更新残留网络
for(int i=v;i!=vs;i=pre[i]){
map[pre[i]][i]-=aug;
map[i][pre[i]]+=aug;
}
u=vs; //从源点开始继续搜
// u=neck; // Dnic 多路增广优化,下次增广时,从瓶颈边(后面)开始
}
}
else
{
//找不到可行弧
int minlevel=vt;
//寻找与当前点相连接的点中最小的距离标号
for(v=1;v<=vt;v++){
if(map[u][v]>0&&minlevel>level[v]){
minlevel=level[v];
}
}
gap[level[u]]--;//(更新gap数组)当前标号的数目减1;
if(gap[level[u]]==0)break;//出现断层
level[u]=minlevel+1;
gap[level[u]]++;
u=pre[u];
}
}
return maxflow;
} int main()
{
int n,m,u,v,cap;
while(~scanf("%d%d",&m,&n))
{
memset(map,0,sizeof(map));
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&cap);
map[u][v]+=cap;
}
printf("%d\n",SAP(1,n));
}
return 0;
}

网络流 最大流—最小割 之SAP算法 详解的更多相关文章

  1. 最大流-最小割 MAXFLOW-MINCUT ISAP

    简单的叙述就不必了. 对于一个图,我们要找最大流,对于基于增广路径的算法,首先必须要建立反向边. 反向边的正确性: 我努力查找了许多资料,都没有找到理论上关于反向边正确性的证明. 但事实上,我们不难理 ...

  2. KM算法详解[转]

    KM算法详解 原帖链接:http://www.cnblogs.com/zpfbuaa/p/7218607.html#_label0 阅读目录 二分图博客推荐 匈牙利算法步骤 匈牙利算法博客推荐 KM算 ...

  3. BM算法  Boyer-Moore高质量实现代码详解与算法详解

    Boyer-Moore高质量实现代码详解与算法详解 鉴于我见到对算法本身分析非常透彻的文章以及实现的非常精巧的文章,所以就转载了,本文的贡献在于将两者结合起来,方便大家了解代码实现! 算法详解转自:h ...

  4. 机器学习经典算法详解及Python实现--基于SMO的SVM分类器

    原文:http://blog.csdn.net/suipingsp/article/details/41645779 支持向量机基本上是最好的有监督学习算法,因其英文名为support vector  ...

  5. Tarjan算法详解

    Tarjan算法详解 今天偶然发现了这个算法,看了好久,终于明白了一些表层的知识....在这里和大家分享一下... Tarjan算法是一个求解极大强联通子图的算法,相信这些东西大家都在网络上百度过了, ...

  6. 安全体系(三)——SHA1算法详解

    本文主要讲述使用SHA1算法计算信息摘要的过程. 安全体系(零)—— 加解密算法.消息摘要.消息认证技术.数字签名与公钥证书 安全体系(一)—— DES算法详解 安全体系(二)——RSA算法详解 为保 ...

  7. 八大排序算法详解(动图演示 思路分析 实例代码java 复杂度分析 适用场景)

    一.分类 1.内部排序和外部排序 内部排序:待排序记录存放在计算机随机存储器中(说简单点,就是内存)进行的排序过程. 外部排序:待排序记录的数量很大,以致于内存不能一次容纳全部记录,所以在排序过程中需 ...

  8. 第三十一节,目标检测算法之 Faster R-CNN算法详解

    Ren, Shaoqing, et al. “Faster R-CNN: Towards real-time object detection with region proposal network ...

  9. SILC超像素分割算法详解(附Python代码)

    SILC算法详解 一.原理介绍 SLIC算法是simple linear iterative cluster的简称,该算法用来生成超像素(superpixel) 算法步骤: 已知一副图像大小M*N,可 ...

随机推荐

  1. c 判断文件或文件夹是否存在,多种方法, 为什么从一开始就不直接来个统一的呢?

    具体内容,请看: https://blog.csdn.net/u012494876/article/details/51204615 判断文件或文件夹是否存在,竟然有这么多方法: GetFileAtt ...

  2. 金融量化分析【day111】:Matplotib-绘制K线图

    一.绘制k线图 1.使用金融包出错解决 1.错误代码 ImportError: No module named finance 2.解决办法 https://github.com/matplotlib ...

  3. NOI-OJ 2.2 ID:3089 爬楼梯

    整体思路 这是一个典型的递归型问题: 临界点:如果只有1级台阶,有1种走法(一次一步):如果有2级台阶,则有2种走法(一次一步或一次两步) 递归方法,对于n级台阶,如果第一次走1步,还剩n-1级台阶, ...

  4. [再寄小读者之数学篇](2014-05-28 Ladyzhenskaya 不等式)

    $$\bex f\in C_c^\infty(\bbR^2)\ra \sen{f}_{L^4}\leq \sqrt{2} \sen{f}_{L^2}^{1/2} \sen{\p_1f}_{L^2}^{ ...

  5. Tomcat zabbix监控、jmx监控、zabbix_java_gateway

    几种方式监控tomcat,如标题. 下面就是参考的网上的连接.自己可以试一下. 由于牵扯到jvm的很多东西, 在这里就只是粘贴处连接参考. http://www.cnblogs.com/chrisDu ...

  6. Linker errors in Android NDK (undefined reference to `__cxa_end_cleanup')

    在 Android 中移植一个库(该库 使用了 libstlport )时 产生如下错误: ./obj/local/armeabi/objs/jniWrapper/native.o: In funct ...

  7. 51nod--1069 Nim 游戏(博弈论)

    题目: 有N堆石子.A B两个人轮流拿,A先拿.每次只能从一堆中取若干个,可将一堆全取走,但不可不取,拿到最后1颗石子的人获胜.假设A B都非常聪明,拿石子的过程中不会出现失误.给出N及每堆石子的数量 ...

  8. python学习第21天

    type和类 继承 抽象类 接口类 多态 java 鸭子类型 pickle模块 collections.namedtuple

  9. JAVA 遍历数组

    package Code411;//遍历数组public class CodeArray { public static void main(String[] args) { int[] array ...

  10. PHP—-模型MODEL 一对多

    假如有两个表,菜品和菜品种类,菜品的关联外键是food_type_id, 所以在菜品的model中应该写 public function foodType(){ return $this->be ...