学习笔记:网络流最大流

引入

使得整个网络的流量最大的流函数叫做最大流。举个栗子,如图 (太丑了太丑了)
在这里插入图片描述

边权表示容量。
我们可以观察到:
如果拆开了来看,
4-5-3-7这条增广路最大流量是3,超过了相当于水管就爆了。
2-5-3-7这条增广路最大流量是2,
2-10这条增广路最大流量是2,
如果合并起来看的话,
发现4-5-3-7如果走了的话,那么最大流量是3,那么其他边的剩余流量就会减少,变成1-2-0-4,这样的话,我们发现2-5-3-7这条路就不能走了。而2-10是可以走的,变成了0-8
这样最大流就是3+2=5了。
但是如果我第一找找的是2-5-3-7呢,那么剩余的容量就是0-3-1-5了,那么4-5-3-7只能流1,2-10不能走了,流量为3。明显更少。
那么这样要怎么办呢?回溯吗?不,我们更优美的方法解决。
我们对于每一条边建上反边,初始边权为0。
沿增广路同向边增流w[i]-=flow,反向边减流w[i^1]+=flow。
注意这里使用了’ ^ '异或符,我们需要在建邻接表时将下标从奇数开始tot=1或-1。

EK

首先介绍EK的思路,每次bfs找增广路,直至找不到了为止,时间复杂度O(nm2)。
代码实现:

#include<bits/stdc++.h>
using namespace std;
#define int long long

const int N=405,M=1e4+5;
int n,m,s,t;
bool vis[N];
int first[N],nex[M],to[M],w[M],tot=1;
struct node
{
    
    
	int v,e;//存该点上一个点和边 
}pre[N];

void add(int x,int y,int z)
{
    
    
	nex[++tot]=first[x];
	first[x]=tot;
	to[tot]=y;
	w[tot]=z;
}

bool bfs()
{
    
    
	queue<int>a;//每次找之前初始化 
	memset(vis,0,sizeof(vis));
	memset(pre,-1,sizeof(pre));
	vis[s]=1;
	a.push(s);
	while(!a.empty())
	{
    
    
		int x=a.front();
		a.pop();
		for(int i=first[x];i;i=nex[i])
		{
    
    
			int y=to[i];
			if(vis[y]==0 && w[i]!=0)//如果没有被搜过,且剩余容量不为0 
			{
    
    
				pre[y].v=x;//y前面是x 
				pre[y].e=i;//y与x之间的边是i 
				if(y==t) return 1;
				vis[y]=1;
				a.push(y);
			}
		}
	}
	return 0;
}

int EK()
{
    
    
	int ans=0;
	while(bfs())//如果存在增广路 
	{
    
    
		int Min=0x3f3f3f3f;
		for(int i=t;i!=s;i=pre[i].v)//找到最小的值,即是最大流量 
			Min=min(Min,w[pre[i].e]);
		for(int i=t;i!=s;i=pre[i].v)
		{
    
    
			w[pre[i].e]-=Min; //增流 
			w[pre[i].e^1]+=Min;//减流 
		}
		ans+=Min;//累计答案 
	}
	return ans; 
}

main()
{
    
    
	scanf("%lld%lld",&m,&n);
	s=1;t=n;
	int x,y,z;
	for(int i=1;i<=m;i++)
	{
    
    
		scanf("%lld%lld%lld",&x,&y,&z);
		add(x,y,z);
		add(y,x,0);
	}
	cout<<EK();
	return 0;
}	

dinic

这里主要介绍dinic算法:
dinic思路是:首先bfs分层,分好层后不断dfs找增广路,并累计答案。时间复杂度O(n2m)。

#include<bits/stdc++.h>
using namespace std;
#define int long long

const int N=1e5+5,M=5e6+5,inf=1<<29;
int first[N],nex[M],to[M],w[M],tot=1;
int n,m,s,t,ans,dep[N];
queue<int>a;

void add(int x,int y,int z)
{
    
    
	nex[++tot]=first[x];
	first[x]=tot;
	to[tot]=y;
	w[tot]=z;
}

bool bfs()
{
    
    
	memset(dep,0,sizeof(dep));//初始化 
	while(!a.empty()) a.pop();
	a.push(s);dep[s]=1;
	while(!a.empty())
	{
    
    
		int x=a.front();a.pop();
		for(int i=first[x];i;i=nex[i])
		{
    
    
			int y=to[i];
			if(w[i] && dep[y]==0)
			{
    
    
				a.push(y);
				dep[y]=dep[x]+1;
				if(y==t) return 1;
			}
		}
	}
	return 0;
}

int dfs(int x,int flow)//flow代表该增广路的最大流量 
{
    
    
	if(x==t) return flow;
	int rest=flow,k,i;
	for(i=first[x];i&&rest;i=nex[i])
	{
    
    
		int y=to[i];
		if(w[i] && dep[y]==dep[x]+1)
		{
    
    
			k=dfs(y,min(rest,w[i]));//继续找,并返回最大值 
			if(!k) dep[y]=0;//这条边没有剩余了,就直接去掉 
			w[i]-=k;
			w[i^1]+=k;
			rest-=k;
		}
	}
	return flow-rest;
}

signed main()
{
    
    
	scanf("%lld%lld%lld%lld",&n,&m,&s,&t);
	int x,y,z;
	for(int i=1;i<=m;i++)
	{
    
    
		scanf("%lld%lld%lld",&x,&y,&z);
		add(x,y,z);
		add(y,x,0);//反边边权为0 
	}
	int flow=0;
	while(bfs())//分层 
		while(flow=dfs(s,inf)) ans+=flow;//不断dfs
		//这里的顺序是先给flow赋值,再判断是否是0或其他数 
	cout<<ans;
	return 0;
}

当前弧优化

我们仅需要一个now[M]数组来记下边,下次枚举直接从这个边开始,但就这一点可以节省大量的时间。
代码实现:和上面的大体相似,只需要增加几行。

#include<bits/stdc++.h>
using namespace std;
#define int long long

const int N=1e5+5,M=5e6+5,inf=1<<29;
int first[N],nex[M],to[M],w[M],tot=1;
int now[M],n,m,s,t,ans,dep[N];
queue<int>a;

void add(int x,int y,int z)
{
    
    
	nex[++tot]=first[x];
	first[x]=tot;
	to[tot]=y;
	w[tot]=z;
}

bool bfs()
{
    
    
	memset(dep,0,sizeof(dep));
	while(!a.empty()) a.pop();
	a.push(s);dep[s]=1;now[s]=first[s];//初始化 
	while(!a.empty())
	{
    
    
		int x=a.front();a.pop();
		for(int i=first[x];i;i=nex[i])
		{
    
    
			int y=to[i];
			if(w[i] && dep[y]==0)
			{
    
    
				a.push(y);
				now[y]=first[y];//初始化 
				dep[y]=dep[x]+1;
				if(y==t) return 1;
			}
		}
	}
	return 0;
}

int dfs(int x,int flow)
{
    
    
	if(x==t) return flow;
	int rest=flow,k,i;
	for(i=now[x];i&&rest;i=nex[i])
	{
    
    
		int y=to[i];
		if(w[i] && dep[y]==dep[x]+1)
		{
    
    
			k=dfs(y,min(rest,w[i]));
			if(!k) dep[y]=0;
			w[i]-=k;
			w[i^1]+=k;
			rest-=k;
		}
	}
	now[x]=i;//记下结束的边,下次直接搜 
	return flow-rest;
}

signed main()
{
    
    
	scanf("%lld%lld%lld%lld",&n,&m,&s,&t);
	int x,y,z;
	for(int i=1;i<=m;i++)
	{
    
    
		scanf("%lld%lld%lld",&x,&y,&z);
		add(x,y,z);
		add(y,x,0);
	}
	int flow=0;
	while(bfs())
		while(flow=dfs(s,inf)) ans+=flow;
	cout<<ans;
	return 0;
}

计算最大流的基本dinic算法就介绍完了。qwq

猜你喜欢

转载自blog.csdn.net/pigonered/article/details/121024033