zoukankan      html  css  js  c++  java
  • HLPP感性瞎扯

    HLPP=侯磊胖胖

    大家好,今天我们来扯HLPP算法了。

    这是一个大毒瘤网络流算法,能够在(O(n^2sqrt{m}))的时间复杂度内跑出一张图的最大流出来。但是,一般的网络流题目考的是建图,而不是丧心病狂的卡常。

    闲话少说,正片开始。

    关于求最大流,一个直观的想法就是从源点灌流量,死命灌,灌到再也没有一滴流量可以活着到达汇点的时候,你就成功了。

    依据增广路定理,这是正确的,因为这时找不到任何一条增广路(不然就有流量能够达到汇点,意味着水没灌够)。我们只需要在灌流量的时候在反向边上同时增加负流量就行。

    显然,我们可以就直接暴力灌,对于每个点i维护(extra_i),表示这个点当前剩余这么多流量。初始(extra_S)赋为(inf),其它点都赋成(0)。之后,维护一个队列,初始为(S)。之后就暴力推进,如果在推进的过程中有点的(extra)被更新,就将它入队。最后答案即为(extra_T)

    (其实还有个细节,就是(S)(T)都不能入队。(S)不能入队是因为一开始(S)能流出的流量全流走了,接下来(S)只能接受走投无路想要回到革命根据地的流量,但是不能再流出来。(T)不能入队则更为直观,长征都结束了,你还想跳入敌人的包围圈?因此一开始不应直接将(S)赋值并入队,而是将与(S)有边((S,y,z))相连的点(y)(extra_y)赋成(z),并不需要初始化(extra_S)。)

    但是,如果你真就照着上述思路把代码敲出来了,你会发现,它陷入了死循环。

    为什么呢?

    考虑点(x)。设有一条边((x,y)),则(extra_y)就可以被(x)更新。

    但是!!!当你更新完(y)后,反向边((y,x))也获得了流量。这意味着流量可以回流!

    怎么办呢?

    这时我们就想到了中国人民的老朋友(Dinic)

    回想一下,在(Dinic)中,相较于(EK),我们引入了高度(height)概念。只有(height_x=height_y+1)的点对((x,y))(x)才有可能(y)更新。

    侯磊胖胖算法能怎么应用它呢?

    这个时候,我们可以从汇点开始,建立反过来的(height)数组。这时,只有(height_x=height_y+1)的点对((x,y))(x)才有可能(y)更新。

    注意一字之差,但实现却大大不同。

    在反向bfs出(height)数组后,程序就可以运行了。

    在一个点(x)推过流量后,如果它还有剩余流量(即(extra_x)不为(0)),这时我们就可以抬高它的(height_x),抬到它所有能推到的点(y)(即所有使得边((x,y))的剩余流量不为(0)的点(y))的(min(height_y)+1)。即:(height_x=min(height_y)+1)。这样就可以保证每次推完之后还能再推(经济发展可持续)。

    那为什么就不会产生反复横跳反复推流的情形呢?

    因为当两个点反复推流,一直推到(height)比源点还高的时候,它们就会把流量流回原点!!!

    所以我们要初始把(height_S)赋成(n),避免在非反复推流的情况下,别的点就急着吐出流量给源点。

    这时候你就认为可以了吗?

    (color{Blue}{TLE})

    暂且估算一下这个暴力推流的程序的复杂度,就可以发现它肯定不止(O(n^2sqrt{m})),甚至连(Dinic)都比不上。(至于准确复杂度是多少呢?我也不知道)

    怎么办呢?这时候,侯磊胖胖的发明者Tarjan(对,就是那个发明强连通分量tarjan算法、LCAtarjan算法、splay等等的大毒瘤)发话了:

    我们可以把暴力中的队列换成以高度为键值的优先队列,每次从队列中取出(height)最高的点,进行推流。剩下的一模一样。

    正确性的瞎扯证明:

    显然,某一时刻队列里的所有元素全都是等价的,就跟拓扑排序一样,可以瞎交换位置而不影响正确性。但是,如果我们取(height)最高的点,它能推出的流量或许比较多(因为比其它点都要高),入队的点也或许比较多(同上),同时自身(height)的变化也或许比较小(同上)。

    因此我们就学完了侯磊胖胖。

    哦,另外,同(Dinic+gap=ISAP)一样,侯磊胖胖也是基于(height)数组的,也可以进行(gap)优化。如果对于某个(height),其节点数为(0)的话,显然就不可能再到达汇点了(因为流量只能在(height)差为(1)的点对间传递)。因此就可以将该(height)的最后一个节点的所有关联点的(height)都赋成(n+1)。(如果你没有听懂,建议先把(ISAP)算法学掉。)

    如果你现在头脑晕晕乎乎昏昏沉沉像喝了电脑配件一样,不要放弃,我们马上把代码分段解析一下。

    I.bfs求出(height)

    queue<int>Q;
    bool bfs(){
    	memset(height,0x3f3f3f3f,sizeof(height)),height[T]=0,Q.push(T);
    	while(!Q.empty()){
    		int x=Q.front();Q.pop();
    		for(int i=head[x];i!=-1;i=edge[i].next)if(height[edge[i].to]==0x3f3f3f3f)height[edge[i].to]=height[x]+1,Q.push(edge[i].to);
    	}
    	return height[S]!=0x3f3f3f3f;
    }
    

    如果你学过Dinic,对它应该没有任何陌生感,只是把(S)改成(T)而已。

    II.推流

    priority_queue<pair<int,int> >q;
    bool in[N];
    void Push_Flow(int x){
    	for(int i=head[x],y,pushaway;i!=-1;i=edge[i].next){
    		y=edge[i].to;
    		if(!edge[i].val||height[edge[i].to]+1!=height[x])continue;//if there is a gap or the edge has been filled, then we shouldn't push flow.
    		pushaway=min(edge[i].val,extra[x]);//we can push away such much flow
    		edge[i].val-=pushaway,edge[i^1].val+=pushaway,extra[x]-=pushaway,extra[y]+=pushaway;//modifying the remaining flow of each edges and nodes
    		if(y!=S&&y!=T&&!in[y])q.push(make_pair(height[y],y)),in[y]=true;//if y isn't S or T, and it is not in the priority_queue, push it in
    		if(!extra[x])return;//the flow of node x has been run out of.
    	}
    }
    

    也比较好理解。能推就推,推不了(有断层)跳过,推完了退出。

    III.重标号(就是提升一个节点的(height))。

    
    void Relabel(int x){
    	height[x]=0x3f3f3f3f;
    	for(int i=head[x];i!=-1;i=edge[i].next)if(edge[i].val)height[x]=min(height[x],height[edge[i].to]+1);
    }
    

    如果你看懂了如何提升(height)的部分,理解它应该不困难。

    IV.主函数

    
    int HLPP(){
    	if(!bfs())return 0;//if S and T are initially unconnected, the max flow is clearly 0.
    	height[S]=n;
    	for(int i=1;i<=n;i++)if(height[i]!=0x3f3f3f3f)gap[height[i]]++;//counting the amounts of each height
    	for(int i=head[S],y,pushaway;i!=-1;i=edge[i].next){//push from S, a little difference between the Push_Flow fuction.
    		y=edge[i].to;
    		if(!edge[i].val)continue;
    		pushaway=edge[i].val;//JUST PUSH!!! S initially has infinite flow.
    		edge[i].val-=pushaway,edge[i^1].val+=pushaway,extra[S]-=pushaway,extra[y]+=pushaway;
    		if(y!=S&&y!=T&&!in[y])q.push(make_pair(height[y],y)),in[y]=true;
    	}
    	while(!q.empty()){//when it can still be updated:
    		int x=q.top().second;q.pop();
    		in[x]=false,Push_Flow(x);
    		if(!extra[x])continue;//if x's flow had all been pushed away, break.
    		if((--gap[height[x]])==0){//if such gap has no nodes:
    			for(int i=1;i<=n;i++)if(i!=S&&i!=T&&height[i]>height[x]&&height[i]<n+1)height[i]=n+1;//assign the related nodes to UNREACHABLE(n+1) 
    		}
    		Relabel(x),gap[height[x]]++,q.push(make_pair(height[x],x)),in[x]=true;//push x back to the priority_queue
    	}
    	return extra[T];
    }
    

    应该比较清晰易懂吧……当一个点的(height>n)时(不管是(n+1)还是(0x3f3f3f3f)),都不会存在一条增广路(因为它最多只有(n)个节点,则最大(height)只能到(n))。

    V.大礼包:

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1210;
    int n,m,head[N],cnt,extra[N],S,T,height[N],gap[N<<1];
    struct node{
    	int to,next,val;
    }edge[240010];
    void ae(int u,int v,int w){
    	edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
    }
    priority_queue<pair<int,int> >q;
    queue<int>Q;
    bool in[N];
    bool bfs(){
    	memset(height,0x3f3f3f3f,sizeof(height)),height[T]=0,Q.push(T);
    	while(!Q.empty()){
    		int x=Q.front();Q.pop();
    		for(int i=head[x];i!=-1;i=edge[i].next)if(height[edge[i].to]==0x3f3f3f3f)height[edge[i].to]=height[x]+1,Q.push(edge[i].to);
    	}
    	return height[S]!=0x3f3f3f3f;
    }
    void Push_Flow(int x){
    	for(int i=head[x],y,pushaway;i!=-1;i=edge[i].next){
    		y=edge[i].to;
    		if(!edge[i].val||height[edge[i].to]+1!=height[x])continue;//if there is a gap or the edge has been filled, then we shouldn't push flow.
    		pushaway=min(edge[i].val,extra[x]);//we can push away such much flow
    		edge[i].val-=pushaway,edge[i^1].val+=pushaway,extra[x]-=pushaway,extra[y]+=pushaway;//modifying the remaining flow of each edges and nodes
    		if(y!=S&&y!=T&&!in[y])q.push(make_pair(height[y],y)),in[y]=true;//if y isn't S or T, and it is not in the priority_queue, push it in
    		if(!extra[x])return;//the flow of node x has been run out of.
    	}
    }
    void Relabel(int x){
    	height[x]=0x3f3f3f3f;
    	for(int i=head[x];i!=-1;i=edge[i].next)if(edge[i].val)height[x]=min(height[x],height[edge[i].to]+1);
    }
    int HLPP(){
    	if(!bfs())return 0;//if S and T are initially unconnected, the max flow is clearly 0.
    	height[S]=n;
    	for(int i=1;i<=n;i++)if(height[i]!=0x3f3f3f3f)gap[height[i]]++;//counting the amounts of each height
    	for(int i=head[S],y,pushaway;i!=-1;i=edge[i].next){//push from S, a little difference between the Push_Flow fuction.
    		y=edge[i].to;
    		if(!edge[i].val)continue;
    		pushaway=edge[i].val;//JUST PUSH!!! S initially has infinite flow.
    		edge[i].val-=pushaway,edge[i^1].val+=pushaway,extra[S]-=pushaway,extra[y]+=pushaway;
    		if(y!=S&&y!=T&&!in[y])q.push(make_pair(height[y],y)),in[y]=true;
    	}
    	while(!q.empty()){//when it can still be updated:
    		int x=q.top().second;q.pop();
    		in[x]=false,Push_Flow(x);
    		if(!extra[x])continue;//if x's flow had all been pushed away, break.
    		if((--gap[height[x]])==0){//if such gap has no nodes:
    			for(int i=1;i<=n;i++)if(i!=S&&i!=T&&height[i]>height[x]&&height[i]<n+1)height[i]=n+1;//assign the related nodes to UNREACHABLE(n+1) 
    		}
    		Relabel(x),gap[height[x]]++,q.push(make_pair(height[x],x)),in[x]=true;//push x back to the priority_queue
    	}
    	return extra[T];
    }
    int main(){
    	scanf("%d%d%d%d",&n,&m,&S,&T),memset(head,-1,sizeof(head));
    	for(int i=1,x,y,z;i<=m;i++)scanf("%d%d%d",&x,&y,&z),ae(x,y,z),ae(y,x,0);
    	printf("%d
    ",HLPP());
    	return 0;
    }
    

    由于我的侯磊胖胖是看着Mr_Spade巨佬的博客学习的,码风可能比较相似,但本博客的内容属于原创。

    总结:

    侯磊胖胖果然是毒瘤算法。但是考试用Dinic就行了。

    完结撒Tarjan~~~

  • 相关阅读:
    Java框架之SpringMVC
    Java进阶之路
    Java入门基础教学(含配置环境变量等)
    Vue 入门学习
    WCF综合运用之:文件断点续传
    爬取集思录数据(1)--强赎表
    爬虫知识点(一)
    已知1、某股票的增减持日期,2、股票从上市至今的交易数据,求减持后(交易日)1天,5天,15天的收盘价。
    从tushare获取增减持数据
    生成文本序列
  • 原文地址:https://www.cnblogs.com/Troverld/p/12781021.html
Copyright © 2011-2022 走看看