最大流的四种常用算法

最大流的四种常用算法

本博客仅用于记录博主自己的代码,只有代码会有注释,若是初学者想进行学习可以移步到最大流 — 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网络流练习题单与题解:网络流练习题单

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值