网络流最大流初步-Push–relabel maximum flow algorithm

简介

做网络流最大流的题,常用的算法就是Dinic's algorithm。时间复杂度为O\left ( V^{2}E \right ),通常由于出题人水平较低,几乎能过所有的题。功利地看,这样就没问题了。但是,站在追求真(zhuang)理(B)的角度上。我们有理由去了解更多的算法。

事实上网络流最大流算法,(在具有正确性的前提下,)大体分两类:增广路(augmenting path)算法和推进-重标号(push-relabel)算法(又称预流推进(preflow-push)算法,括号外面的是我自己翻译的)。前一类很常用,常见的同时也是比较优秀的有Edmonds-Karp Algorithm(O\left ( VE^{2} \right ))和Dinic's algorithm(O\left ( V^{2}E \right ))。后类个体之间的区别在于优化方式,普通的做法复杂度达到了O\left ( V^{2}E \right ),队列优化(此种优化后的算法又称HLPP,Highest label preflow push algorithm 最高标号预流推进算法)后是O\left ( V^{3} \right ),优先队列优化后是O\left ( V^{2}\sqrt{E} \right ),还有一种动态树优化,时间复杂度达到了O(VElog_{\frac{V^{2}}{E}}),可以说是非常优秀了。还有一些算法如KRT's algorithm,资料不是太多,我也不会。感兴趣的可以翻阅资料。

这篇文章主要说一下普通的最大流推进-重标号算法(General push-relabel maximum flow algorithm)用优先队列的优化,因为优化前后区别很小,理解难度一般。

算法

不同于增广路算法,这类算法是对网络流的一个模拟。在这里,我们用手来操控水,钦定它们的流向。为避免环的出现(会有死循环),我们在三维的空间里想象一下,将每个节点变成一个水坑,在平置的情况下,水显然是静止的。那推进的方法,就是我们钦定出每个点的高度,模拟一下水就好了。这里需要强调的是,水只能一层一层地流,即很平缓地流,也就是路程里面不能出现类似于瀑布的陡峭的山崖(steep arc,纯属意译)。遵循顺序来说,源点应是最高的,汇点应是最低的,故我们令源点高度为n(点个数)。

这种算法和其它的都类似,有一个试的过程,但是,怎么知道你试错了呢?怎么改正呢?首先,就要知道错了会怎样。第一个错误就是走错路,这里依旧使用增广路的想法(逃不过的)。你去了多少,反向边流量就加多少,该去的就算是绕一圈也能回来。第二个错误就是高度的设置,因为你是钦定的,总可能有问题。高度不对出现的问题就在于:水跑到山谷里去了,前面的边又往上走,形成了堰塞湖。尽管你可以钦定高度,但你不能钦定题目。这个的解决方法即是改。怎么改呢?形成了湖的地方地势较低,我们把它抬上去就好了,抬到刚刚好能往外流的地方。所以初始的点的高度(除源点)为0。这里有个小优化,你要找需要抬到的地方又需要循环一次,怪麻烦。直接发现有问题,一次抬一格,再走到这里就再看,需要就抬。尽管复杂度不变,但编程的难度减小了些。这时又会有个问题,万一你把它抬起来,它原来所处的平面就没有其它点了,这个水不也断流了么。即使下面有点,你高差太大,就不行了。所以我们记录每个平面上点的个数,如果安排过后出现这种情况,我们把其它在它上面的点直接举到n+1的高度,就能倒流回源点了(可以证明出现这种情况,那些点也没用了)。

基础的做法就是每次枚举每个点,显然复杂度较高,我们用一个优先队列来存被推过水的点。又可以证明,当每次最先弄高度最高的点是最快的;反之,弄最低的是最慢的。这两种时间复杂度为O\left ( V^{2}\sqrt{E} \right )O\left ( V^{2}E \right )

代码

有一些细节问题并不好表述或是我懒得表述。请参见代码,有注释。题目参见:Luogu3376。

#include
using namespace std;
const int inf=0x3f3f3f3f;
int n,m;//The number of points & edges
int s,t;
int first[100005],nex[1000005],cap[1000005],to[1000005],tot=1;
//  first edge of a point
//               next edge of a edge
//                           capacity of a edge
//                                       the ending to a edge
//                                                   cnt of a edge
long long flow[100005];//the remaining water in a point
int cnt[100005];//count of heights
int h[100005];//heights
struct node
{
	int i,h;//The point & the height of the point
	node(int i=0,int h=0):i(i),h(h){}
	bool operator < (const node &a) const { return h q;//the priority queue of active nodes
void add_edge(int u,int v,int w)
{
	tot++;
	nex[tot]=first[u];
	first[u]=tot;
	to[tot]=v;
	cap[tot]=w;
	tot++;
	nex[tot]=first[v];
	first[v]=tot;
	to[tot]=u;
	cap[tot]=0;
	return;
}
//Push some water from u to v through e.
int push(int u,int v,int e)
{
	int delta=min((long long)cap[e],flow[u]);//'some water'
	cap[e]-=delta;
	cap[e^1]+=delta;
	flow[u]-=delta;
	flow[v]+=delta;
	return delta;
}
long long maxflow()
{
	//initialisation
	while(!q.empty())q.pop();
	memset(flow,0,sizeof(flow));memset(h,0,sizeof(h));memset(cnt,0,sizeof(cnt));
	//push the s
	h[s]=n;
	//Set the usable water in s infinitive
	flow[s]=inf;
	q.push(node(s,h[s]));
	//Do the push operations.
	while(!q.empty())
	{
		//Get the point. Pop the top.
		int u=q.top().i;
		q.pop();
		//Make sure there's remaining water.
		if(!flow[u])
			continue;
		for(int e=first[u],v=to[e];e;e=nex[e],v=to[e])
		{
//              Make sure the arc is valid
			if((u==s||h[u]==h[v]+1)
//                Push some water and make sure the pushed isn't null.
				&&push(u,v,e)
//                You can't push the s/t into the queue, or you'll err.
                &&v!=s&&v!=t)
//              Push the next one into the queue.
				q.push(node(v,h[v]));
		}
//         If there's remaining things in the point which means there's a lake,
		if(u!=s&&u!=t&&flow[u])
		{
//			   If the height is wrong,
			if(!(--cnt[h[u]]))
			{
//				make all the inappropriate points' heights higher than s which are intouchable.
				for(int i=1;i<=n;++i)
				{
					if(i!=s&&i!=t&&h[i]>h[u]&&h[i]<=n)h[i]=n+1;
				}
			}
			++cnt[++h[u]];//Highten the point.
			q.push(node(u,h[u]));//Push it into the queue again.
		}
	}
	return flow[t];//The remaining water in the t is the maximum of flow.
}
int main()
{
	ios::sync_with_stdio(0);//fasten the input/output
	cin>>n>>m>>s>>t;
	for(int i=0;i>u>>v>>w;
		add_edge(u,v,w);
	}
	cout<

网络流最大流初步-Push–relabel maximum flow algorithm_第1张图片

大家要相信它是最快的,是因为洛谷奇怪的数据才如此慢的。

其它

我觉得以后就写这个了。1复杂度低;2符合认知思维还短,没有奇怪的dfs、bfs。

你可能感兴趣的:(算法初步系列)