网络流补充——最大流与最小割

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/wljoi/article/details/102732798

网络流的定义

网络流(network-flows)是一种类比水流的解决问题方法,与线性规划密切相关。

引入

在具体讲解网络流之前,我们用一个例子来理解一下:

假设有一个自来水厂,自来水厂无限量地通过一条条水管向每家每户运输自来水,而我们在用完自来水后的废水也要通过一些管道流向一个废水站(容量可视为无穷大),管道有一定容量,问废水站最多可以收集多少水。

易知这是一个有向图。

那么解决这个问题的经典算法就是——网络流。

概念明确


容量:每条边最大的流量(上图边中间的数字,水管的容量)

源点:起点(上图中 4 4 号点,自来水厂)

汇点:终点(上图中 3 3 号点,废水站)

流:从源点到汇点的一条合法路径(上图中 4 2 3 4-2-3 ,自来水厂 - - 废水站)

流量:每条边各自被经过的次数(上图中 4 2 3 4-2-3 给每条边增加了 20 20 的流量)

限制

每条边的流量不超过容量。

每条边的流入量等于流出量。

实现(増广路方法)

FF算法

具体讲解

1. 1. 找到一条从源点到汇点的路径,我们称其为増广路。

2. 2. 找到流过的路径剩余流量的最小值,记为 v v

3. 3. 将答案加上 v v

4. 4. 将该路径上所有边的剩余流量减去 v v ,其反向边的剩余流量加上 v v

重复上述步骤知道找不到路径为止。

会被极端数据卡死。

EK算法

对FF算法的优化,每次找最短路进行増广。

时间复杂度上界 O ( n m 2 ) O(nm^2)

大多数情况跑不满上界。

Dinic算法

EK的复杂度太高,需要优化。

先给每条边赋一个 1 1 的权值,bfs找到每个点到源点的最短路。

如果源点到汇点的最短路不存在,即不存在増广路,退出。

dfs找所有的増广路,进行一些操作。

显然的是,如果増广一次后最短路没有任何变化,那么我们还是可以继续使用那条最短路,就不用bfs了。

时间复杂度: O ( n 2 m ) O(n^2m)

二分图最大匹配时间复杂度(所有边容量为 1 1 ): O ( m n ) O(m\sqrt n)

ISAP(Improved Shortest Augumenting Path)算法

这个是今天的重点。

我们观察到Dinic算法每次dfs是都要进行一次bfs,那么我们有没有办法进一步优化呢?

当然有。

这就是我们的ISAP算法。

观察到每一次求増广路时对我们的层次图改变不大,那么我们就只求一次层次图,在dfs里直接修改层次图来优化时间复杂度。

实现

从汇点到源点跑一遍bfs,找出最短路。

从源点到汇点进行dfs,如果一个点从前面的点传入的流量大于其流出的流量,那么这个点在当前深度就没有用处了,我们将深度++。

重复上述操作直到出现断层,即某个深度没有点,那么就不存在増广路,直接退出。

下面用代码加深理解。

#include<bits/stdc++.h>
using namespace std;
int n,m,s,t;
int tot=1,first[200005],nxt[200005],to[200005],w[200005],dep[200005],cnt[200005];
int Read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')  f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=(x<<3)+(x<<1)+ch-'0';
		ch=getchar();
	}
	return x*f;
}
void Add(int x,int y,int z){
	nxt[++tot]=first[x];
	first[x]=tot;
	to[tot]=y;
	w[tot]=z;
}
void Bfs(int t){
	memset(dep,0xff,sizeof(dep));
	dep[t]=0;
	cnt[0]=1;
	queue<int> q;
	q.push(t);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int e=first[u];e;e=nxt[e]){
			if(dep[to[e]]==-1){
				dep[to[e]]=dep[u]+1;
				++cnt[dep[to[e]]];
				q.push(to[e]);
			}
		}
	}
}
int mf=0;
int dfs(int p,int f){
	if(p==t){
		mf+=f;
		return f;
	}
	int u=0;
	int e;
	for(e=first[p];e;e=nxt[e]){
		if(w[e]&&dep[to[e]]==dep[p]-1){
			int uu=dfs(to[e],min(w[e],f-u));
			if(uu){
				w[e]-=uu;
				w[e^1]+=uu;
				u+=uu;
			}
			if(u==f)  return u;    //如果流入等于流出,那么就结束
		}
	}
	//此时流入必定大于流出,我们操作一下这个点
	if(!--cnt[dep[p]]){  //出现断层
		dep[s]=n+1;
	}
	++cnt[++dep[p]];
	return u;
}
signed main(){
	n=Read(),m=Read(),s=Read(),t=Read();
	for(int i=1;i<=m;i++){
		int U=Read(),V=Read(),W=Read();
		Add(U,V,W);
		Add(V,U,0);
	}
	Bfs(t);
	while(dep[s]<n){
		dfs(s,0x3fffffff);
	}
	cout<<mf<<endl;
	return 0; 
}

关于ISAP和Dinic的复杂度

二分图Dinic快,一般图ISAP快。

理论复杂度上界一样。

猜你喜欢

转载自blog.csdn.net/wljoi/article/details/102732798