图论 —— 网络流 —— 最大流 —— SAP 算法与 ISAP 算法

2024-06-17 21:08

本文主要是介绍图论 —— 网络流 —— 最大流 —— SAP 算法与 ISAP 算法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【概述】

EK 算法直接进行增广,而 Dinic 则是每次进行 bfs 求出层次图再 dfs 沿着层次图进行多路增广。

然而,Dinic 中每次进行 bfs 求层次图有些浪费,因为层次图的改动并不是很大,在这种思路下,因此考虑直接在每次 dfs 增广的时候修改层次图来优化求最短路的过程。

SAP(Shortest Augment Path),顾名思义是找最短的增广路,将 EK 算法 O(V*E*E) 的时间优化到了 O(V*V*E)

而比赛中常用的是 ISAP(Improved Shortest Augment Path)算法,其在 SAP 的基础上加了对当前弧的优化和对间隙的优化,通过 dfs 中不断修改层次标号 level 的方式省去了每次的 bfs,所以称为 improved。

ISAP 的时间复杂度上界与 SAP 相同,同样是 O(V*V*E)

【基本思想】

Dinic 算法中,需要每次搜索出层次图,而在 ISAP 中,只需要每次在 dfs 的过程中修改层次标号。

具体来说,用 level[x] 表示残余网络上 x 到汇点 t 的最短层次,每次沿着 level[x] = level[v] + 1 的路增广,如果点 x 的出边的点没有发现满足这个条件,那么说明当前的最短路已经过时,需要修改层次标号。

修改层次标号,就是让 x 可以至少有一个点能够增广,所以取所有 level[v] 中最小的那个加一即可,这样增广下去,当 level[s]>=n 时,结束算法,即:s 到 t 的距离大于等于 n 时,说明至少有一个点经过了两次,即不存在增广路。

【优化过程】

  1. 若一开始将层次标号都设为 0,那么 dfs 最多需要 O(n*n) 来将标号初始化,但可以最开始逆向 bfs 一次,在 O(n+m) 的范围内初始化所有层次标号
  2. 若层次标号出现了断层,那么显然不存在新的增广路。可以用一数组 gpa 来记录每种层次标号有多少个,若当前修改最后一个某种层次标号,那么就出现了前后断层,从而结束算法
  3. 增广过程中,若一个点的标号没有修改过,那么它已经遍历过的边不需要再遍历一次,因此存下每次遍历到哪条边,下一次从这条边开始遍历(可能到这里后流量用完但还未增广完)
  4. 最短路的修改具有连续性,即不需要每次求后继的标号最小值,而是直接给标号加一
  5. 同 Dinic,若流量用完,直接退出

【实现】

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<vector>
#define INF 0x3f3f3f3f
#define N 1001
using namespace std;
struct Edge{int from,to;int flow,cap;Edge(){}Edge(int from,int to,int cap,int flow):from(from),to(to),cap(cap),flow(flow){}
};
int n,m;
int S,T;
bool vis[N];//逆向bfs的标记数组
int level[N];//记录层次
int father[N];//标记父节点
int gap[N];//记录每组层次标号有几个
int cur[N];//当前正访问i节点的第cur[i]条弧
vector<Edge> edge;
vector<int> G[N];void addEdge(int from,int to,int cap){//添边edge.push_back(Edge(from,to,cap,0));edge.push_back(Edge(to,from,0,0));int m=edge.size();G[from].push_back(m-2);G[to].push_back(m-1);
}
int augument(int x,int cp){while(x!=S){//寻找最小残量Edge &e=edge[father[x]];cp=min(cp,e.cap-e.flow);x=edge[father[x]].from;}x=T;while(x!=S){//增广edge[father[x]].flow+=cp;edge[father[x]^1].flow-=cp;x=edge[father[x]].from;}return cp;
}
void bfs(){//逆向bfsmemset(vis,false,sizeof(vis));level[T]=0;vis[T]=1;queue<int> Q;Q.push(T);while(!Q.empty()){int x=Q.front();Q.pop();for(int y=0;y<G[x].size();y++){Edge &e=edge[G[x][y]];if(!vis[e.from]&&e.cap>e.flow){vis[e.from]=true;level[e.from]=level[x]+1;Q.push(e.from);}}}
}
int ISAP(){//根据情况前进或后退,走到汇点时增广bfs();memset(cur,0,sizeof(cur));memset(gap,0,sizeof(gap));for(int i=0;i<n;i++)gap[level[i]]++;int x=S;int flow=0;while(level[S]<n){if(x==T){//走到汇点,进行增广flow+=augument(T,INF);x=S;//增广后回到源点}bool flag=false;for(int y=cur[x];y<G[x].size();y++){Edge &e=edge[G[x][y]];if(level[x]==level[e.to]+1&&e.cap>e.flow){flag=true;father[e.to]=G[x][y];//记录来时走的父边cur[x]=y;x=e.to;//前进break;}}if(!flag){//无法前进,后退int m=n-1;//若没有弧,则m+1=n,即level[i]=nfor(int y=0;y<G[x].size();y++){Edge &e=edge[G[x][y]];if(e.cap>e.flow)m=min(m,level[e.to]);}//gap优化if(--gap[level[x]]==0)//如果走不动了,且这个距离值原来只有一个,那么s-t不连通break;gap[level[x]=m+1]++;cur[x]=0;if(x!=S)//退一步,沿父边返回x=edge[father[x]].from;}}return flow;
}
int main(){while(scanf("%d%d",&n,&m)!=EOF&&(n+m)){memset(level,0,sizeof(level));for(int i=0;i<n;i++)G[i].clear();edge.clear();for(int i=0;i<m;i++){int x,y,w;scanf("%d%d%d",&x,&y,&w);addEdge(x,y,w);}S=1,T=n;printf("%d\n",ISAP());}return 0;
}

 

这篇关于图论 —— 网络流 —— 最大流 —— SAP 算法与 ISAP 算法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1070490

相关文章

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

SpringBoot实现MD5加盐算法的示例代码

《SpringBoot实现MD5加盐算法的示例代码》加盐算法是一种用于增强密码安全性的技术,本文主要介绍了SpringBoot实现MD5加盐算法的示例代码,文中通过示例代码介绍的非常详细,对大家的学习... 目录一、什么是加盐算法二、如何实现加盐算法2.1 加盐算法代码实现2.2 注册页面中进行密码加盐2.

SpringBoot使用OkHttp完成高效网络请求详解

《SpringBoot使用OkHttp完成高效网络请求详解》OkHttp是一个高效的HTTP客户端,支持同步和异步请求,且具备自动处理cookie、缓存和连接池等高级功能,下面我们来看看SpringB... 目录一、OkHttp 简介二、在 Spring Boot 中集成 OkHttp三、封装 OkHttp

Java时间轮调度算法的代码实现

《Java时间轮调度算法的代码实现》时间轮是一种高效的定时调度算法,主要用于管理延时任务或周期性任务,它通过一个环形数组(时间轮)和指针来实现,将大量定时任务分摊到固定的时间槽中,极大地降低了时间复杂... 目录1、简述2、时间轮的原理3. 时间轮的实现步骤3.1 定义时间槽3.2 定义时间轮3.3 使用时

Linux系统之主机网络配置方式

《Linux系统之主机网络配置方式》:本文主要介绍Linux系统之主机网络配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、查看主机的网络参数1、查看主机名2、查看IP地址3、查看网关4、查看DNS二、配置网卡1、修改网卡配置文件2、nmcli工具【通用

使用Python高效获取网络数据的操作指南

《使用Python高效获取网络数据的操作指南》网络爬虫是一种自动化程序,用于访问和提取网站上的数据,Python是进行网络爬虫开发的理想语言,拥有丰富的库和工具,使得编写和维护爬虫变得简单高效,本文将... 目录网络爬虫的基本概念常用库介绍安装库Requests和BeautifulSoup爬虫开发发送请求解

如何通过Golang的container/list实现LRU缓存算法

《如何通过Golang的container/list实现LRU缓存算法》文章介绍了Go语言中container/list包实现的双向链表,并探讨了如何使用链表实现LRU缓存,LRU缓存通过维护一个双向... 目录力扣:146. LRU 缓存主要结构 List 和 Element常用方法1. 初始化链表2.

golang字符串匹配算法解读

《golang字符串匹配算法解读》文章介绍了字符串匹配算法的原理,特别是Knuth-Morris-Pratt(KMP)算法,该算法通过构建模式串的前缀表来减少匹配时的不必要的字符比较,从而提高效率,在... 目录简介KMP实现代码总结简介字符串匹配算法主要用于在一个较长的文本串中查找一个较短的字符串(称为