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 网络最大流 为例)相关推荐

  1. 洛谷P3376 网络最大流

    不是讲网络流,就是存个板子 另外我的Dinic跑得比EK慢一倍可还行( 附两份比较好的教程,均来自洛谷日报 EK\sf \color{blue}EKEK Dinic\sf \color{blue}Di ...

  2. 洛谷3376 网络最大流

    题目描述 如题,给出一个网络图,以及其源点和汇点,求出其网络最大流. 输入输出格式 输入格式: 第一行包含四个正整数N.M.S.T,分别表示点的个数.有向边的个数.源点序号.汇点序号. 接下来M行每行 ...

  3. sscanf小技巧-洛谷P7911 网络连接

    sscanf小技巧-洛谷P7911 网络连接 序言 本题解来源:本蒟蒻上课摸鱼 (别学我) 最近学了一个挺好用的函数--sscanf(),结合洛谷P7911 网络连接讲一下. (点击查看题目) 推荐几 ...

  4. 洛谷P3376 【模板】网络最大流

    P3376 [模板]网络最大流 题目描述 如题,给出一个网络图,以及其源点和汇点,求出其网络最大流. 输入输出格式 输入格式: 第一行包含四个正整数N.M.S.T,分别表示点的个数.有向边的个数.源点 ...

  5. 【洛谷P3376】网络最大流【网络流】

    分析 网络流算法本身是之前学过的,今天拿出来复习打个板子. 最原始的思路应该是搜索每一条路,每次进行增广的操作,知道不能增广为止.显然,这种思路复杂度比较高. 如何进行优化?就是dinic算法.上面那 ...

  6. 洛谷3171 网络吞吐量(网络流)

    t开成n结果cur赋值的时候也只赋值到t令人智熄 [题目分析] 好吧我承认这个错误真的呵呵........ 题目有那~~~~~么长,然后画画图这道题就基本看出正解了,再一看数据范围,n<=500 ...

  7. 洛谷 P3376 【模板】网络最大流

    题目描述 如题,给出一个网络图,以及其源点和汇点,求出其网络最大流. 输入输出格式 输入格式: 第一行包含四个正整数N.M.S.T,分别表示点的个数.有向边的个数.源点序号.汇点序号. 接下来M行每行 ...

  8. 【洛谷 - P3376 】【模板】网络最大流

    题干: 如题,给出一个网络图,以及其源点和汇点,求出其网络最大流. 输入输出格式 输入格式: 第一行包含四个正整数N.M.S.T,分别表示点的个数.有向边的个数.源点序号.汇点序号. 接下来M行每行包 ...

  9. 洛谷 P3128 [USACO15DEC]最大流Max Flow

    题意简述 给定一颗树,每次操作可以使两个点最短路上的点+1,求最大的点 题解思路 树上差分 若操作u, v,则++f[u], ++f[v], --f[lca(u, v)], --f[father(lc ...

  10. 洛谷 - P4015 运输问题(费用流)

    题目链接:点击查看 题目大意:有n个卖家和m个买家,每个卖家会卖ai个物品,每个买家会买bi个物品,每个卖家向每个卖家卖东西会有一定的代价,问如何匹配才能让代价最小/最大 题目分析:和上一道题大同小异 ...

最新文章

  1. linux下载tomcat7命令,linux下安装tomcat7.0
  2. mysql表数据以本地文件方式导入Hive
  3. java里冒泡排序编程案例_冒泡排序法-java案例详解
  4. python tracer函数_Python流程控制常用工具和函数定义
  5. 中图分类法---- U 交通运输
  6. 2018计算机核心期刊,2018中国科技核心期刊目录!!!!
  7. 斑马打印机linux驱动安装教程,win7系统安装斑马打印机驱动的操作方法
  8. 啊哈C语言——让计算机多彩的开口说话
  9. 【Android实战】json解析+GridView自适应布局+图片加载
  10. [51nod 1051 最大子矩阵和]前缀和+dp
  11. 导数与微分及简单例题
  12. 经典sql server基础语句大全
  13. 笔记本外接显示器教程级后续使用技巧
  14. 个人看过的动漫、动画电影推荐
  15. 中国成全球最大工业机器人市场 年增长速度25%
  16. 0.96OLED图标取模,包括信号图标,蓝牙图标,闹钟,电池
  17. TIA portal西门子博途安装时一直提示重启怎么办?
  18. 课上——HTML 表格 学生成绩表
  19. Unity:人物跳跃动画的切换
  20. Eclipse下开发WAP网站

热门文章

  1. 数据分析:同比-环比
  2. Mac 忘记管理员名和密码
  3. 下载bilibili视频
  4. 为markdown目录标题添加序号
  5. 监督学习、无监督学习、半监督学习、强化学习、自监督学习
  6. 二元线性方程组与二阶行列式
  7. java大文件下载失败_java文件下载超过4G文件错误
  8. 什么是自组织竞争网络?
  9. 手机无线投屏到linux电脑,scrcpy - 手机无线投屏到电脑
  10. 多个安卓设备投屏到电脑_安卓手机怎么投屏到电脑上?这样做,在电脑上就能操控手机...