最大网络流的多种解法(洛谷P3376 网络最大流 为例)
P3376 【模板】网络最大流 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P3376
本题是一个最大网络流的模版题,我也是刚刚学完最大网络流,所以就借此机会,练练手。
关于最大网络流的定义这里就不多做介绍(口齿不清)。如果不知道的可以去先看一下大佬的文章
这里给出一个链接(8条消息) 网络流之最大流算法(EdmondsKarp)_Yoangh的博客-CSDN博客_最大流算法https://blog.csdn.net/y990041769/article/details/21026445?ops_request_misc=&request_id=&biz_id=102&utm_term=%E6%9C%80%E5%A4%A7%E7%BD%91%E7%BB%9C%E6%B5%81&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-3-21026445.142^v2^pc_search_result_control_group,143^v4^control&spm=1018.2226.3001.4187
那么回归到这题。
下面将用4种解法来对这题进行解答。
Edmons_Karps算法 (采用的是Ford_Fulkerson方法)
使用BFS来计算增广路径,由于这种算法复杂度高,只能用于小图,所以邻接矩阵存图即可
Ford_Fulkerson方法的思路大致是
1. 初始所有边的流量为0
2.找到一条s -> t的路径按三大性质得到该路径的最大流
3.更新这次搜索结束后的残流网络。
不断重复2,3过程直到找不到路径
第一次提交代码(73分 Wa了3个点)
#include<bits/stdc++.h>
using namespace std;//一些习惯的简化
#define el '\n'
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define lop(i, a, b) for(int i = (a); i < (b); i++)
#define dwn(i, a, b) for(int i = (a); i >= (b); i--)
#define ms(a, b) memset(a, b, sizeof(a))typedef long long LL;
const int MAXN = 250;
const LL INF = 9223372036854775807;
int n, m;
int edge[MAXN][MAXN];
int pre[MAXN];
LL bfs(int s, int t){ //增广路搜索LL flow[MAXN];memset(pre, -1, sizeof(pre));flow[s] = INF, pre[s] = 0;queue<int> Q;Q.push(s);while(!Q.empty()){int u = Q.front();Q.pop();if(u == t)break;rep(i, 1, n){if(i != s && edge[u][i] && pre[i] == -1){pre[i] = u;Q.push(i);flow[i] = min(flow[u], (LL)edge[u][i]);}}}if(pre[t] == -1)//已无路径return -1;return flow[t];
}
LL Edmonds_Karp(int s, int t){ //使用Ford_Fulkerson方法bfs + 更新残流网络LL maxflow = 0;while(1){LL nowflow = bfs(s, t);if(nowflow == -1)break;int cur = t;while(cur != s){ //更新残流网络int fa = pre[cur];edge[cur][fa] += nowflow;edge[fa][cur] -= nowflow;cur = fa;}maxflow += nowflow;}return maxflow;
}
int main(){int s, t;cin >> n >> m >> s >> t;rep(i, 1, m){int u, v, w;cin >> u >> v >> w;edge[u][v] = w;}cout << Edmonds_Karp(s, t);return 0;
}
过了8个点可以说整体代码思路是没问题的,那么问题出在哪呢?看了一下,感觉应该是被卡long long等等数据出现操作问题了所以应该只需要修改一下细节就可以了。 然后把longlong改完发现还是不行,那是什么问题呢?????
下载了测试样例8,发现,居然有很多重边。。。(我还是太水了)
调整了一下输入,重边的叠加,然后改long long果然就过了
#include<bits/stdc++.h>
using namespace std;//一些习惯的简化
#define el '\n'
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define lop(i, a, b) for(int i = (a); i < (b); i++)
#define dwn(i, a, b) for(int i = (a); i >= (b); i--)
#define ms(a, b) memset(a, b, sizeof(a))typedef long long LL;
const int MAXN = 250;
const int INF = 0x3f3f3f3f;
const LL LNF = 9223372036854775807;
int n, m;
LL edge[MAXN][MAXN];
int pre[MAXN];
LL bfs(int s, int t){ //增广路搜索LL flow[MAXN];memset(pre, -1, sizeof(pre));flow[s] = LNF, pre[s] = 0;queue<int> Q;Q.push(s);while(!Q.empty()){int u = Q.front();Q.pop();if(u == t)break;rep(i, 1, n){if(i != s && edge[u][i] && pre[i] == -1){pre[i] = u;Q.push(i);flow[i] = min(flow[u], edge[u][i]);}}}if(pre[t] == -1)//已无路径return -1;return flow[t];
}
LL Edmonds_Karp(int s, int t){ //使用Ford_Fulkerson方法bfs + 更新残流网络LL maxflow = 0;while(1){LL nowflow = bfs(s, t);if(nowflow == -1)break;int cur = t;while(cur != s){ //更新残流网络int fa = pre[cur];edge[cur][fa] += nowflow;edge[fa][cur] -= nowflow;cur = fa;}maxflow += nowflow;}return maxflow;
}
int main(){int s, t;cin >> n >> m >> s >> t;rep(i, 1, m){int u, v, w;cin >> u >> v >> w;if(!edge[u][v])edge[u][v] = w;//反边其实已经初始化为0elseedge[u][v] += w;}cout << Edmonds_Karp(s, t);return 0;
}
Dinic算法
下面的 Dinic 可解决 FF 效率低的问题。
每次多路增广:u 点通过一条边,向 v 输出流量以后,v 会尝试到达汇点(到达汇点才真正增广),然后 v 返回实际增广量。这时,如果 u 还有没用完的供给,就继续尝试输出到其它边。
但是要警惕绕远路、甚至绕回的情况,不加管制的话极易发生。怎么管?
源点顺着残量网络想要到达其它点,需要经过一些边对吧?按照经过的边数(即源点出发以后的距离)把图分层,即用 bfs 分层。 每次尝试给予时,只考虑给予自己下一层的点,就可以防止混乱。
综合上面两条。每回合也是从源点出发,先按照当前残量网络分一次层,随后多路增广,尽可能增加流量。增广过程中,会加入一些反向边,这些反向边逆着层次图,本回合并不会走。所以还需要进入下一回合。一直到 bfs 分层时搜不到汇点(即残量网络断了)为止。
上诉引用大佬思路 https://www.luogu.com.cn/blog/cicos/Dinic
然后就是代码段还是一样要注意开long long,然后我们这里用前向星的储存方法写,代码中有很多细节比如cnt 必须从 1开始否者会错误等等都在代码中有详细注释
#include<bits/stdc++.h>
using namespace std;//一些习惯的简化
#define el '\n'
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define lop(i, a, b) for(int i = (a); i < (b); i++)
#define dwn(i, a, b) for(int i = (a); i >= (b); i--)
#define ms(a, b) memset(a, b, sizeof(a))typedef long long LL;
const int MAXN = 200010;
const int INF = 0x3f3f3f3f;
const LL LNF = 9223372036854775807;
int n, m, s, t;
struct edge{LL w;int to, next;
}e[MAXN];//前向星法储存
int head[10001];
int cnt;
LL ans = 0;void init(){//初始化rep(i, 0, n){head[i] = -1;e[i].next = -1;}cnt = 1;//必须从1开始,如果从0开始会有错误???
}void addage(int u, int v, int w){//前向星加边e[++cnt].to = v;e[cnt].w = w;e[cnt].next = head[u];head[u] = cnt;
}int dep[10001];
int l, r, que[10001];//手写队列
bool fc_bfs(){//bfs分层记录memset(dep, 0, sizeof(dep));dep[s] = 1;// que[l = r = 1] = s;queue<int> Q;Q.push(s);//while(l <= r){while(!Q.empty()){//int u = que[l++];int u = Q.front();Q.pop();for(int i = head[u]; i != -1; i = e[i].next){int v = e[i].to;if(e[i].w && !dep[v]){dep[v] = dep[u] + 1;//que[++r] = v;Q.push(v);}}}return dep[t];//如果dep[t] = 0 说明已经无增广路可搜到汇点
}
LL Dinic(int u, LL in){//u代表出发点,in代表的是当前u可供给的水量(in不一定会用完)if(u == t)//已经到达汇点return in;LL flow = 0;//记录从u到达终点的最大流量for(int i = head[u]; i != -1 && in; i = e[i].next){int v = e[i].to;if(e[i].w && dep[v] == dep[u] + 1){//只遍历下一层LL res = Dinic(v, min(in, e[i].w));e[i].w -= res;e[i ^ 1].w += res;//反边残流in -= res;flow += res;}}if(flow == 0)//剪枝dep[u] = 0;//代表这个点无法达到汇点可以去除,以后不再遍历return flow;
}
int main(){cin >> n >> m >> s >> t;init();rep(i, 1, m){int u, v, w;cin >> u >> v >> w;addage(u, v, w);addage(v, u, 0);//反边先加上}// rep(u, 1, n){ //判断前向星准确性// for(int i = head[u]; i != -1 ; i = e[i].next){// cout << e[i].to << " " << e[i].w << el;// }// cout << el;// }while(fc_bfs())//如果能找到增广路ans += Dinic(s, LNF);//初始点会水量无限大cout << ans;return 0;
}
最后写2种最高深也是最快的算法ISAP与HLPP
如果想要了解更多的可以去看看这个大佬的文章https://www.luogu.com.cn/blog/ONE-PIECE/jiu-ji-di-zui-tai-liu-suan-fa-isap-yu-hlpp
ISAP(Improved Shortest Augumenting Path)
在 dinic 中,我们要跑许多遍 bfs ,这就有可能导致算法效率不高。
于是 ISAP 就这样出现了,它只需要跑一遍 bfs !
大体运行过程如下:
1.从汇点t开始反向跑回s进行一次bfs,层数从t为0层开始叠加
2.从源点到t再进行dfs,这里末尾需要维护2个数组dep[]和gap[]
dep[]还是记录层数
gap[]记录处于某层的点数数量
3.重复(2)操作直到出现断层(gap[u] == 0)退出
其他细节就看代码里的吧。
#include<bits/stdc++.h>
using namespace std;//一些习惯的简化
#define el '\n'
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define lop(i, a, b) for(int i = (a); i < (b); i++)
#define dwn(i, a, b) for(int i = (a); i >= (b); i--)
#define ms(a, b) memset(a, b, sizeof(a))typedef long long LL;
const int MAXN = 200010;
const int INF = 0x3f3f3f3f;
const LL LNF = 9223372036854775807;
int n, m, s, t;
struct edge{LL w;int to, next;
}e[MAXN];//前向星法储存
int head[10001];
int cur[10001];
int cnt;
LL maxflow = 0;void init(){//初始化rep(i, 0, n){head[i] = -1;e[i].next = -1;}cnt = 1;//必须从1开始,如果从0开始会有错误???
}void addage(int u, int v, int w){//前向星加边e[++cnt].to = v;e[cnt].w = w;e[cnt].next = head[u];head[u] = cnt;
}int dep[10001], gap[10001];
int l, r, que[10001];//也可以手写队列
void fc_bfs(){//bfs分层记录memset(dep, -1, sizeof(dep));memset(gap, 0, sizeof(gap));gap[0] = 1; //汇点t为第0层dep[t] = 0;// que[l = r = 1] = t;queue<int> Q;Q.push(t);//while(l <= r){while(!Q.empty()){//int u = que[l++];int u = Q.front();Q.pop();for(int i = head[u]; i != -1; i = e[i].next){int v = e[i].to;if(dep[v] == -1){//这里注意反跑判断不考虑边权值dep[v] = dep[u] + 1;gap[dep[v]]++;//que[++r] = v;Q.push(v);}}}return;
}
LL dfs(int u, LL in){//u代表出发点,in代表的是当前u可供给的水量(in不一定会用完)if(u == t){//已经到达汇点maxflow += in;return in;}LL flow = 0;//记录从u到达终点的最大流量for(int i = cur[u]; i != -1; i = e[i].next){cur[u] = i; //当前弧优化int v = e[i].to;if(e[i].w && dep[v] + 1 == dep[u]){//只遍历下一层LL res = dfs(v, min(in, e[i].w));e[i].w -= res;e[i ^ 1].w += res;//反边残流in -= res;flow += res;if(in == 0)//流已经全部流完了return flow;}}//能到这里说明in还有剩余但是不会再使用了隔开点u和后续的所有vif(--gap[dep[u]] == 0) dep[s] = n + 1;//断层退出dep[u]++;gap[dep[u]]++;return flow;
}LL ISAP(){maxflow = 0;fc_bfs();while(dep[s] < n){memcpy(cur, head, sizeof(head)); //初始当前弧优化dfs(s, LNF);}return maxflow;
}
int main(){cin >> n >> m >> s >> t;init();rep(i, 1, m){int u, v, w;cin >> u >> v >> w;addage(u, v, w);addage(v, u, 0);//反边先加上}// rep(u, 1, n){ //判断前向星准确性// for(int i = head[u]; i != -1 ; i = e[i].next){// cout << e[i].to << " " << e[i].w << el;// }// cout << el;// }cout << ISAP();return 0;
}
到这里我们已经可以发现从开始的代码到现在,时间复制度不断优化,越来越快了,但还没完,还有更快的那就是最终HLPP
最终HLPP(这种方法我还没有写,所以思路可参见前面引用的大佬文章%%%)
最终附上4种算法的时间测试
序号 | Dinic | FF | EKEK | 终极 HLPP | ISAP |
---|---|---|---|---|---|
1 | 0.625s | TLE | 0.171s | 0.125s | 0.265s |
2 | 0.562s | TLE | 0.156s | 0.093s | 0.265s |
3 | 0.828s | TLE | 0.625s | 0.093s | 0.390s |
4 | 0.578s | TLE | 0.312s | 0.093s | 0.328s |
5 | 2.468s | 24.000s | 0.046s | 0.078s | 0.218s |
6 | 5.546s | TLE | 0.078s | 0.140s | 0.203s |
7 | 5.218s | 10.984s | 0.109s | 0.125s | 0.328s |
8 | 7.812s | 49.953s | 0.218s | 0.109s | 0.265s |
9 | 1.281s | TLE | 0.375s | 0.078s | 0.375s |
10 | 0.781s | TLE | 0.156s | 0.062s | 0.187s |
11 | 0.312s | TLE | 0.046s | 0.093s | 0.203s |
12 | 0.875s | TLE | 2.703s | 0.078s | 0.328s |
13 | 0.703s | TLE | 0.156s | 0.156s | 0.203s |
14 | 0.500s | TLE | 0.328s | 0.109s | 0.218s |
15 | 0.296s | TLE | 0.171s | 0.109s | 0.296s |
16 | 0.562s | TLE | 0.234s | 0.125s | 0.296s |
17 | 4.687s | TLE | 0.140s | 0.093s | 0.343s |
18 | 2.921s | TLE | 0.031s | 0.156s | 0.296s |
19 | 2.359s | TLE | 0.040s | 0.078s | 0.312s |
20 | 4.656s | TLE | 0.078s | 0.062s | 0.390s |
21 | 0.500s | TLE | 0.312s | 0.093s | 0.218s |
22 | 1.000s | TLE | 0.203s | 0.109s | 0.234s |
23 | 0.343s | TLE | 0.062s | 0.156s | 0.265s |
24 | 1.015s | TLE | 0.281s | 0.140s | 0.328s |
总用时 | 46.428s | - | 7.037s | 2.553s | 6.754s |
上面测试转自https://www.luogu.com.cn/blog/181775/solution-p3376
如果你看到了这里,感谢你看完了我的(废话)。
最大网络流的多种解法(洛谷P3376 网络最大流 为例)相关推荐
- 洛谷P3376 网络最大流
不是讲网络流,就是存个板子 另外我的Dinic跑得比EK慢一倍可还行( 附两份比较好的教程,均来自洛谷日报 EK\sf \color{blue}EKEK Dinic\sf \color{blue}Di ...
- 洛谷3376 网络最大流
题目描述 如题,给出一个网络图,以及其源点和汇点,求出其网络最大流. 输入输出格式 输入格式: 第一行包含四个正整数N.M.S.T,分别表示点的个数.有向边的个数.源点序号.汇点序号. 接下来M行每行 ...
- sscanf小技巧-洛谷P7911 网络连接
sscanf小技巧-洛谷P7911 网络连接 序言 本题解来源:本蒟蒻上课摸鱼 (别学我) 最近学了一个挺好用的函数--sscanf(),结合洛谷P7911 网络连接讲一下. (点击查看题目) 推荐几 ...
- 洛谷P3376 【模板】网络最大流
P3376 [模板]网络最大流 题目描述 如题,给出一个网络图,以及其源点和汇点,求出其网络最大流. 输入输出格式 输入格式: 第一行包含四个正整数N.M.S.T,分别表示点的个数.有向边的个数.源点 ...
- 【洛谷P3376】网络最大流【网络流】
分析 网络流算法本身是之前学过的,今天拿出来复习打个板子. 最原始的思路应该是搜索每一条路,每次进行增广的操作,知道不能增广为止.显然,这种思路复杂度比较高. 如何进行优化?就是dinic算法.上面那 ...
- 洛谷3171 网络吞吐量(网络流)
t开成n结果cur赋值的时候也只赋值到t令人智熄 [题目分析] 好吧我承认这个错误真的呵呵........ 题目有那~~~~~么长,然后画画图这道题就基本看出正解了,再一看数据范围,n<=500 ...
- 洛谷 P3376 【模板】网络最大流
题目描述 如题,给出一个网络图,以及其源点和汇点,求出其网络最大流. 输入输出格式 输入格式: 第一行包含四个正整数N.M.S.T,分别表示点的个数.有向边的个数.源点序号.汇点序号. 接下来M行每行 ...
- 【洛谷 - P3376 】【模板】网络最大流
题干: 如题,给出一个网络图,以及其源点和汇点,求出其网络最大流. 输入输出格式 输入格式: 第一行包含四个正整数N.M.S.T,分别表示点的个数.有向边的个数.源点序号.汇点序号. 接下来M行每行包 ...
- 洛谷 P3128 [USACO15DEC]最大流Max Flow
题意简述 给定一颗树,每次操作可以使两个点最短路上的点+1,求最大的点 题解思路 树上差分 若操作u, v,则++f[u], ++f[v], --f[lca(u, v)], --f[father(lc ...
- 洛谷 - P4015 运输问题(费用流)
题目链接:点击查看 题目大意:有n个卖家和m个买家,每个卖家会卖ai个物品,每个买家会买bi个物品,每个卖家向每个卖家卖东西会有一定的代价,问如何匹配才能让代价最小/最大 题目分析:和上一道题大同小异 ...
最新文章
- linux下载tomcat7命令,linux下安装tomcat7.0
- mysql表数据以本地文件方式导入Hive
- java里冒泡排序编程案例_冒泡排序法-java案例详解
- python tracer函数_Python流程控制常用工具和函数定义
- 中图分类法---- U 交通运输
- 2018计算机核心期刊,2018中国科技核心期刊目录!!!!
- 斑马打印机linux驱动安装教程,win7系统安装斑马打印机驱动的操作方法
- 啊哈C语言——让计算机多彩的开口说话
- 【Android实战】json解析+GridView自适应布局+图片加载
- [51nod 1051 最大子矩阵和]前缀和+dp
- 导数与微分及简单例题
- 经典sql server基础语句大全
- 笔记本外接显示器教程级后续使用技巧
- 个人看过的动漫、动画电影推荐
- 中国成全球最大工业机器人市场 年增长速度25%
- 0.96OLED图标取模,包括信号图标,蓝牙图标,闹钟,电池
- TIA portal西门子博途安装时一直提示重启怎么办?
- 课上——HTML 表格 学生成绩表
- Unity:人物跳跃动画的切换
- Eclipse下开发WAP网站