加载中…

返回上一页

网络流初步

什么是网络

网络,顾名思义就是一组有向图的集合 G.
每一条边都有一个相应的容量.

在网络中,有两个常用的点:源点汇点.
源点一般记作 s,汇点一般记作 t.

举个例子:

下面这张图就是一个网络.

它的每一个边上的权值叫做它的容量. 这些容量组成的集合叫做网络流.

网络最大流

网络中的每个节点,如果它的一个汇入流量大小可以等于它的流出流量大小,那么它是一个可行流.
在整张网络中,能够流通整个网络且流量最大的那个流量叫做这个网络的最大流.

如何求最大流?

(这张图来自课件,挺经典的,拿来用了.)

你的脑子里一定一下闪过一个念头:搜索.

没错,就是搜索.

如何搜索?

dfs,从 s 点出发,遇到已经访问过的点(碰到环)就返回. 遇到 t 点证明找到了一条路径,然后回溯更新.

bfs,仍然从 s 点出发,记录一下前驱,搜到 t 后返回更新.

但是这样只能找到其中一条(或几条)路径,并不能完全遍历到所有的路径. 对于这张图而言 dfs 时会先往点 1 走,然后从 2 流向 t,流过 2 流量. 回溯,从 1t 也有一条路径,流过 2 流量. 然后不能再走了,得到了错误结果.

追究一下它错在哪里:
在流过流量以后,直接累加了答案并标记. 这导致如果有其它路径是无法加入的.

所以我们需要进行一个类似于贪心反悔的操作.

下面引入一个概念:增广路.

增广路的定义:

对于可行流的一条链P,如果满足:

  1. P中所有的前向弧的流量小于容量
  2. P中所有的后向弧的流量均大于零

(其实就是前向弧还能继续增加流量,后向弧还能继续减少流量),这样的链便是一条增广路.

增广一次以后剩余容量构成的网络叫做残量网络.

那么你一定会想到一种方法:
不断增广当前的网络,直至不能增广为止.

没错!

不断地在残量网络中寻找增广路,最后得到的总流量就是这个网络的最大流了.

(还是课件的那张图,这样能更方便理解)

FF 算法

对于每一个边,如果每次都分别讨论是搜索还是增广太麻烦. 那么把每一条有向边加一个边权为 0 的反向边,这样正向搜索没有流量,就不会遍历;而增广时会用到.

还是那张图,建完反边后就是这样:

每一次在当前网络上进行 dfs 操作寻找路径,记录最小的剩余容量,找到汇点后回溯并减少遍历到边的剩余容量. 反复操作直至无法再找到汇点. 那么答案就是每次记录的容量之和.

#define Ford-Fulkerson
#include<bits/stdc++.h>
#define ll long long
#define rg register
#define rll rg ll
#define maxn 500001
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
static inline ll read()
{
	rll f=0,x=0;rg char ch=getchar();
	while(ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();
	return f?-x:x;
}
static inline void write(rll x)
{
	if(x<0) putchar('-'),x=-x;
	if(x>9) write(x/10);putchar(x%10|'0');
}
struct node
{
	ll to,v,pair/*配对边*/;
};
ll n,m,s,t,k,ans;
ll dep[maxn],v[maxn];
bool vis[maxn];
vector<node> g[maxn];
static inline ll dfs(rll x,rll inv)
{
	if(x==t) return inv;
	vis[x]=0;
	for(rll i=0;i<g[x].size();i++)
	{
		rll to=g[x][i].to,k;
		if((!g[x][i].v)||vis[to]) continue;// 没有残量
		if((k=dfs(to,min(inv,g[x][i].v)))>0)
		{// 这里取 min 是因为顺着往下流要受到前面的最小流量限制
			g[x][i].v-=k;// 残量减少
			g[g[x][i].to][g[x][i].pair].v+=k;// 反边容量增加,方便将来增广
			return k;
		}
	}
	return 0;
}
int main()
{
	n=read();m=read();s=read();t=read();
	for(rll i=1,u,v,w;i<=m;i++) u=read(),v=read(),w=read(),g[u].push_back((node) { v,w,g[v].size() }),g[v].push_back((node) { u,0,g[u].size()-1 });
	while(memset(vis,0,sizeof(vis))&&(k=dfs(s,LLONG_MAX))>0) ans+=k;// 不断增广至不能增广为止
	write(ans);
	return 0;
}

可以发现这个算法每次寻找路上最小的边的权值,而且经常会绕远路,因此它单次增广的复杂度是 O(V+E) 的(即与边和点数有关).

然而这种做法很容易被卡掉,比如这张图:

34 的权为 1,其余均为 999999. 这样它就会不断在 (1,2) 上面推来推去斗智斗勇,不停地增广,导致效率极低.

(还有一种方法叫做 EK 算法,就是把 dfs 换成 bfs,这里不注重讲这种方法,有兴趣的可以了解. 推荐博客

如何改进?

Dinic 算法

Dinic 算法其实就是将 FF 进行一些优化,具体有下面几点:

  1. 每次进行多路增广,从起始点 u 开始搜索,向到达点 v 输出流量,点 v 再出发直至汇点. 回溯回来 v 会返回增广量. 这时如果 u 位置仍有残量,继续去输出到其它边;

  2. 使用 bfs 按照经过的边数将图分层,每次流出流量只考虑下一层的点,避免混乱;

  3. 榨干优化(当前弧优化):如果这条边的剩余流量为 0,那么它没有利用价值,就不再遍历这条边对应的下一个点;

  4. 删点优化:如果已遍历过一条从 uv 的边且 v 点没有输出流量,就将 dep[v] = 0,下次就不会再遍历这条边.

这样单次增广的复杂度是 O (V E) 的,最多一共进行 Vdfs,因此总复杂度是 O (V 2E) 的.

#define Dinic
#include<bits/stdc++.h>
#define ll long long
#define rg register
#define rll rg ll
#define maxn 500001
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
static inline ll read()
{
	rll f=0,x=0;rg char ch=getchar();
	while(ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();
	return f?-x:x;
}
static inline void write(rll x)
{
	if(x<0) putchar('-'),x=-x;
	if(x>9) write(x/10);putchar(x%10|'0');
}
struct node
{
	ll to,v,pair;
};
ll n,m,s,t,ans;
ll dep[maxn],v[maxn];
vector<node> g[maxn];
queue<ll> q;
static inline bool bfs()
{
	while(!q.empty()) q.pop();memset(dep,0,sizeof(dep));
	q.push(s);dep[s]=1;
	while(!q.empty())
	{
		rll x=q.front();q.pop();
		for(rll i=0;i<g[x].size();i++)
		{
			rll to=g[x][i].to;
			if(g[x][i].v&&(!dep[to])) dep[to]=dep[x]+1,q.push(to);
		}
	}
	return dep[t];
}
static inline ll dfs(rll x,rll inv)
{
	if(x==t) return inv;rll outv=0;
	for(rll i=0;i<g[x].size();i++)
	{
		rll to=g[x][i].to,k;
		if(g[x][i].v&&dep[to]==dep[x]+1)
		{
			k=dfs(to,min(inv,g[x][i].v));
			g[x][i].v-=k;g[g[x][i].to][g[x][i].pair].v+=k;
			inv-=k;outv+=k;
		}
	}
	if(!outv) dep[x]=0;
	return outv;
}
int main()
{
	n=read();m=read();s=read();t=read();
	for(rll i=1,u,v,w;i<=m;i++) u=read(),v=read(),w=read(),g[u].push_back((node) { v,w,g[v].size() }),g[v].push_back((node) { u,0,g[u].size()-1 });
	while(bfs()) ans+=dfs(s,LLONG_MAX);write(ans);
	return 0;
}

未完待续

(每个字都是我手敲的,如有误笔请提出并见谅)

posted @ 2022-09-28 10:00  1Liu  阅读(44)  评论(0编辑  收藏  举报