P3376 【模板】网络最大流 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P3376
本题是一个最大网络流的模版题,我也是刚刚学完最大网络流,所以就借此机会,练练手。
关于最大网络流的定义这里就不多做介绍(口齿不清)。如果不知道的可以去先看一下大佬的文章
那么回归到这题。
下面将用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;//反边其实已经初始化为0
else
edge[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和后续的所有v
if(--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
如果你看到了这里,感谢你看完了我的(废话)。