_pks 'w blog

_pks 'w blog

除了勇气,我什么都不缺。

题解 P3376 【【模板】网络最大流】

posted on 2018-10-06 15:16:00 | under 题解 |

看到题解里没有预留推进的网络流233

打算水水 $qwq$

#define $u$ 的伴点集合 与 $u$ 相隔一条边的且 $u$ 能达到的点的集合

前言

$HLPP(Highest~Label~Preflow~Push)$ 最高标签预流推进算法是处理网络最大流里两种常用方法——增广路&预流推进中,预流推进算法的一种。据传由 $tarjan$ 发明怎么又是他 ,并被其他科学家证明了其复杂度是紧却的 $O(n^2\sqrt m)$ 。在随机数据中不逊色于普通的增广路算法,而在精心构造的数据中无法被卡,所以是一种可以替代 $Dinic$ 的方法(随我怎么说,代码又长又难调,所以还是 $Dinic$ 好啊 $\rm{TAT}$ )

但无论怎样, $wiki$ 里面已经承认 $HLPP$ 是现在最优秀的网络流算法了。

那么预流推进这个大门类里面,思想都差不多。大抵上就是我们对每个点记录超额流( $Extra~Flow$ ) ,即允许流在非源点暂时存储,并伺机将超额流推送出去。不可推送的,就会流回源点。那么最终答案显然存储在 $Extra[T]$ 里面。

但同时这也有一个问题,就是会出现两个点相互推送不停的情况。为了防止这样,我们采用最高标号的策略,给每个点一个高度,对于一个点 $u$ 以及它的伴点集合 $\{v\}$ ,当且仅当 $h_u = h_v + 1$ 时才可以推送流。并且我们对于源点 $S$ ,设置 $h_S = N$ ,并对于 $S$ 实行无限制推送。那么最后的答案就保存在 $Extra[T]$ 里面 。

但有时,我们发现有个点是”谷“,即周围点的高度都比它高,但是它有超额流。那么我们此时考虑拔高它的高度,即重贴标签( $relabel$ )操作。

算法流程&优化

以下我们用 $Extra_u$ 表示 $u$ 的超额流, $h_u$ 表示 $u$ 的高度,用 $f_k$ 表示边 $k$ 的容量。

·首先把所有的 $h_i$ 都置为零,并把 $h_s$ 置为 $N$ (点数)。

·将 $S$ 的流推送到每个与 $S$ 相邻的点,同时把他们加入一个以高度为键值得大根堆,所以每次取出的应该是高度最高的、且超额流不为零的点,并执行推送操作。

·对于点 $u$ 推送过程中,如果 $Extra_u$ 减到了 $0$ ,就立即退出(优化一)

·对于每条出边 $k$ ,推送的流量 $F = min(f_k,Extra_u)$ 并执行两个点( $u,v$ )的超额流增减。如果 $v$ 不在堆里面,要把 $v$ 放到堆里面。

·如果推送完毕 $Extra[u]$ 不为零,那么从他的伴点集合选取一个高度最小的点并记录它的高度 $h_{min}$ ,则新的 $h_u = h_{min}+1$ ,并把 $u$ 入堆。

好的,然后就可以撒花了

接下来我们发现,重贴标签的过程似乎与 $ISAP$ 有点点像……所以我们不妨通过一个 $Gap$ 数组来记录”断层情况“:即如果对于一个点 $u$ 来说,他的伴点集 $\{v\}$ 已经不存在 $h_u = h_v + 1$ 的点了,并且也不存在一个点 $j$ 使得 $h_j = h_u$ 那么这个地方就是一个断层 $(Gap)$ ,那么也就是说,对于所有 $h_i> h_u$ 的点来说,它们把流推送到 $h_u$ 的高度就不能继续推送了,所以我们直接 $h_i = N + 1$ ,让他们回流到源点。(优化二)

接下来这个优化,亲测可以提速 $4000ms$ ,平均每个测试点提速 $700$ ~ $800ms$ ,去掉数据最小的点,每个店平均提速 $1000ms$ 。这就是—— $BFS$ !

我们不妨一开始就倒着 $BFS$ 一遍,搜出每个点离汇点的最短距离作为初始高度而不是把零作为初始高度(源点高度还是 $N$ 。嗯, $Mr\_Spade$ 大佬实在太强了 $qwq$

对了,代码实现方面,需要好多判断不是源点和汇点的小细节……无路赛无路赛无路赛 $>\_<$ !

#include <bits/stdc++.h>
#define min Min
#define MAXN 40100
#define MAXM 300010
#define to(k) E[k].to
#define Inf 1926081700

using namespace std ;
struct state{
    int num, h ;
    bool operator <(const state & now) 
    const{  return h < now.h ; }
} ; priority_queue <state> heap ; 
struct edge{
    int to, next, f ;
}E[MAXM] ;  bool vis[MAXN] ; queue <int> q ;
int N, M, S, T, cnt = -1, A, B, C, D, t, min_h ;
int head[MAXN], Extra[MAXN], H[MAXN], Gap[MAXN], node ;

inline int Min(int a, int b){return a > b ? b : a ;}
inline void Preflow_Push(){
    register int i, k ;
    for (i = 1 ; i <= N ; ++ i) 
        if(H[i] < Inf) ++ Gap[H[i]] ;
    for(k = head[S]; k != -1 ; k = E[k].next)
        if((t = E[k].f)){
            E[k].f -= t, E[k ^ 1].f += t, Extra[S] -= t, Extra[to(k)] += t ;
            if(to(k) != T && !vis[to(k)])
                heap.push((state){to(k), H[to(k)]}), vis[to(k)] = 1 ;
        }
    while(!heap.empty()){
        vis[node = heap.top().num] = 0, min_h = Inf, heap.pop() ;
        for(k = head[node] ; k != -1 ; k = E[k].next){
            if(E[k].f && H[node] == H[to(k)] + 1){
                t = min(Extra[node], E[k].f) ;
                E[k].f -= t, E[k ^ 1].f += t, Extra[node] -= t, Extra[to(k)] += t ;
                if(!vis[to(k)] && to(k) != S && to(k) != T)
                    heap.push((state){to(k), H[to(k)]}), vis[to(k)] = 1 ;
            }
            if (E[k].f) min_h = min(min_h, H[to(k)]) ;
            if (!Extra[node]) break ;
        }
        if(Extra[node]) {
            if (!--Gap[H[node]])    
                for(i = 1; i <= N ; ++ i)
                    if(i != S && i != T && H[i] > H[node] && H[i] < N + 1) H[i] = N + 1 ;
            H[node] = Inf; H[node] = min_h + 1 ; 
            heap.push((state){node, H[node]}), vis[node] = 1, ++ Gap[H[node]] ;
        }
    }
}
inline int read() {
    int k = 0 ; char c = getchar();
    while(c < '0' || c > '9') c = getchar() ;
    while(c >= '0' && c <= '9') k = (k << 3) + (k << 1) + c - 48, c = getchar() ;
    return k ;
}
int main(){
    register int i, k ;
    cin >> N >> M >> S >> T ;
    for (i = 1 ; i <= N ; ++ i) 
        head[i] = -1, H[i] = Inf ;
    while(M --){
        A = read(), B = read(), C = read() ;
        E[++ cnt].f = C, E[cnt].to = B, E[cnt].next = head[A], head[A] = cnt ;
        E[++ cnt].f = 0, E[cnt].to = A, E[cnt].next = head[B], head[B] = cnt ;
    }
    q.push(T), H[T] = 0 ;
    while(!q.empty()){
        int now = q.front() ; q.pop() ;
        for(k = head[now] ; k != -1 ; k = E[k].next)
            if (H[to(k)] > H[now] + 1)
                H[E[k].to] = H[now] + 1, q.push(E[k].to) ;
    }
    if (H[S] == 0) {cout << 0 << endl ; return 0 ;} 
    H[S] = N, Preflow_Push() ; cout << Extra[T] << endl ;
}

后记:

·感谢 $Mr\_Spade$ 大佬,他的博客里有许多好玩的东西

·我并不知道那些在 $LuoguP4722$ 最优解第一页的dalao们为什么都这么快,并且代码都一样……直到我翻开了 $wiki$ 提供的 $C$ 语言源码……我的天, $discharge$ 、 $excess$ 这些名字都一样……当然有可能是我神经过敏了233

·扒翻出来的好东西

inline int Min(int a, int b){return a & ((a - b) >> 31) | b & ( ~ (a - b) >> 31) ;}

亲测可以快 $200ms$ !

·别问我能不能用到费用流,我懒得思考qwq

·别想了,随机数据肯定还是Dinic比较好——起码常数小啊