最大流的四种常用算法
本博客仅用于记录博主自己的代码,只有代码会有注释,若是初学者想进行学习可以移步到最大流 — Edmond Karp算法里面主要讲述了最大流的EK算法,另外的几种算法也有推荐的大佬博客基本可以学会最大流算法。
最大流算法可以说有五种(FF , EK ,Dinic, ISAP, HLPP)
第一种是最暴力的dfs来实现的基本都会有超时的风险,所以不贴出代码
基础模板题:P3376 【模板】网络最大流
进阶毒瘤模板题:P4722 【模板】最大流 加强版 / 预流推进
值得一提的是前四种算法都是基于不断寻找增广路来实现的,而最后一个算法HLPP则是基于预流推进的原理来实现的,可以先学会前四种算法把模板过了后再去学习HLPP。
在进阶毒瘤题中,在没有各种玄学优化的情况下只有HPLL算法才能通过此题
嗯,欢迎各位大佬对我的代码进行指点,纠错,感谢!
第一种 Edmond Karp算法
#include<queue>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define ll long long
const int N=205;
const ll INF=1e18;
int n,pre[N];//标记前继节点
ll e[N][N];//存图
bool vis[N];
bool bfs(int s,int t)
{
queue< int >q;
memset(pre, -1, sizeof(pre));
memset(vis, 0, sizeof(vis));
pre[s] = s;
vis[s] = 1;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=1;i<=n;i++)
{
if(e[u][i]&&!vis[i])
{
vis[i]=1;
pre[i]=u;
if(i==t)
return 1;
q.push(i);
}
}
}
return 0;
}
ll EK(int s,int t)
{
ll maxflow = 0, d;//初始化为0流
while(bfs(s,t)) //bfs判断是否还存在增广路
{
d = INF;//最值
printf("%d\n",maxflow);
for(int i=t;i!=s;i=pre[i]) //通过记录的前继节点向前访问
d = min(d,e[pre[i]][i]);//记录最小的delta
for(int i=t;i!=s;i=pre[i])//更新容量
{
e[pre[i]][i] -= d; //通过减小容量达到减小可过流量的目的
e[i][pre[i]] += d; //反向边容量增加相同的大小
}
maxflow += d;
}
return maxflow;
}
int main()
{
ll w;
int i,m,s,t,u,v;
scanf("%d%d%d%d",&n,&m,&s,&t);
for(i=0;i<m;i++)
{
scanf("%d%d%lld",&u,&v,&w);
e[u][v]+=w; //有重边时等于容量增加
}
printf("%lld",EK(s,t));
return 0;
}
许多大佬都是使用链式前向星来存图的,单独存放边的容量,而我就不同了,奇奇怪怪思路清奇的用vector存边,再用邻接矩阵存边权。
第二种 Dinic算法
#include <queue>
#include <iostream>
#include <string.h>
#include <algorithm>
using namespace std;
#define ll long long
const int N = 210, M = 5010;
const ll inf = 1e18;
int n,m,s,t,head[N],tot = 1;//注意要从1开始 即第一条边存储在e[2]中
struct edge
{
int to,nex,w;
}e[M * 2];
void add(int from,int to,int w)
{
e[++ tot].w = w;
e[tot].to = to;
e[tot].nex = head[from];
head[from] = tot;
}
int dep[N];//用bfs分层
bool bfs()//判断是否还存在增广路
{
memset(dep,0,sizeof dep);
queue<int>q;
q.push(s);
dep[s] = 1;
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i = head[u]; i; i = e[i].nex)
{
int v = e[i].to;
if(e[i].w && !dep[v]){
dep[v] = dep[u] + 1;
q.push(v);
}
}
}
return dep[t];
}
ll dfs(int u,ll inflow)//in为进入的流,源点的流无限大
{
if(u == t)//到达汇点
return inflow;//返回这一条增广路的流量
ll outflow = 0;
for(int i = head[u]; i && inflow; i = e[i].nex)//还有残余流量
{
int v = e[i].to;
if(e[i].w && dep[v] == dep[u] + 1)//只搜索下一层次的点,防止绕回或走反向边
{
ll flow = dfs(v , min(1ll * e[i].w,inflow));//min选取边残余容量与入流残余的最小值
e[i].w -= flow; //减去达到汇点的增广流量
e[i ^ 1].w += flow; //反向边增加流量
inflow -= flow; //入流减少相同的
outflow += flow; //增广路增加流量
}
}
if(outflow == 0) dep[u]=0;//通过u点不能到达汇点,剪枝
return outflow;
}
int main()
{
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=0;i<m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,0);
}
ll ans = 0;
while(bfs())
ans += dfs(s ,inf);
printf("%lld",ans);
return 0;
}
因为链式前向星存储边 正向反向边的下标是相邻的,初始化 t o t = 1 tot = 1 tot=1使得所有正向边的下标 i d x idx idx都是偶数,那么所有的反向边 = i d x X O R 1 idx XOR 1 idxXOR1
第三种 ISAP算法
#include<queue>
#include<vector>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define ll long long
const int N=1205;
const ll INF=1e18;
int s,t,n,dep[N],gap[N];//dep点记录深度,gap[i]记录深度为i的点数量
ll e[N][N];//存边权
vector<int>g[N];//存边
void bfs()
{
//与其他增广路算法不同之处在于bfs从汇点向源点跑
dep[t]=1;
gap[1]++;
queue<int>q;
q.push(t);
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(!dep[v])
{
dep[v]=dep[u]+1;
gap[dep[v]]++;
q.push(v);
}
}
}
}
ll dfs(int u,ll flow){
if(u==t)
return flow;
ll used=0;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(e[u][v]&&dep[v]+1==dep[u]){
ll res=dfs(v,min(e[u][v],flow-used));
e[u][v] -= res;
e[v][u] += res;
used += res;
}
if(used==flow)return used;//进入此点的流量全部用完直接返回
}
//前半段和Dinic一模一样
//如果已经到了这里,说明该点出去的所有点都已经流过了
//并且从前面点传过来的流量还有剩余
//则此时,要对该点更改dep
//使得该点与该点出去的点分隔开可以去去寻找另一条增广路
//从此点开始往前所有点的深度都会+1
--gap[dep[u]];
if(gap[dep[u]]==0) dep[s]=n+1;//出现断层无法到达t了
dep[u]++;//层数上升
gap[dep[u]]++;
return used;
}
int main()
{
int m,i,u,v;
ll w,ans=0;
scanf("%d%d%d%d",&n,&m,&s,&t);
for(i=0;i<m;i++)
{
scanf("%d%d%lld",&u,&v,&w);
g[u].push_back(v), e[u][v]+=w;//+=重边
g[v].push_back(u);
}
bfs();
while(dep[s]<=n)
ans+=dfs(s,1e18);
printf("%lld",ans);
return 0;
}
第四种 HLPP算法
有时间的话补充…其实是我还没学会
2022.09.08更新Dinic 模板(vector + 邻接矩阵 -> 链式前向星)
2022.11.17网络流练习题单与题解:网络流练习题单