【题解】P4722 【模板】最大流 加强版 / 预流推进

CHAPTER 0 废话

1.常见的最大流算法可以大致分为两种:找增广路预流推进
2.从理论时间复杂度上界来看,找增广路算法的最坏时间复杂度为\(O(n^2m)\),而预流推进算法的最坏时间复杂度为\(O(n^2\sqrt{m})\),看起来预流推进要显然优于找增广路,但在实际应用(尤指OI)中,由于包括但不限于以下两个原因,预流推进使用频率远不如Dinic等找增广路算法。

网络最大流这一类型题的难点一般在于建模而不是算法本身,故只要出题人不毒瘤,一般不会给出只有预流推进算法才能通过的数据范围

对于随机图,Dinic等找增广路算法的实际表现可能不输于预流推进(类似于SPFA与Dijkstra的关系)

3.此题解是本奆弱调题调了一上午并提炼总结各位讨论区的各位大犇的经验呢和数篇优秀的题解的产物,旨在帮助更多人更高效地理解预流推进算法并尽可能少走弯路浪费时间。由于本人非常没有石粒,题解不足错误之处还请各位批评指正。

CHAPTER 1 大体思想

HLPP

接下来我们介绍预流推进算法中常用的一种:HLPP
想必看这篇题解的人都已经学会了找增广路式网络流算法,在找增广路算法与预留推进算法较为相似的部分,我们可能略讲,区别之处,我们则详细讲解。我们现在继续借用“水流”“水管”等物象辅助理解HLPP算法

超额流

不同于找增广路算法全程保证了流的合法性,在预流推进算法中,顾名思义,可以“预测”将来的流量,也不用保证流的合法性
具体地,我们除了汇源点之外的点都看作水库,其中可以存储无限多“预测将要流出”的流量,不难想到,当算法执行到最后得到合法的流方案时,所有“水库”储存的“流”都应该为0(因为流进来多少就流出去多少)

高度

与找增广路算法给图分层类似的,预流推进算法中引入了高度这一概念,就像找增广路算法中流只能从前一层流向后一层,预流推进算法中的流只能从高度为\(high\)的点流向高度为\(high-1\)的点
这时考虑一种尴尬的情况,如果有个点的的高度为\(high\),但是它所连接的所有点中没有既满足边流量未满又满足高度为\(high-1\)的,也就是“水库”里的水流不出去了。
那么,如何科学的设定/维护/改变所有节点的高度呢?

重贴标签

解决的思路非常简单,只要找到一个高度的值,将点的高度改为这个值,让“水库”里的水正好可以往外流就可以啦

CHAPTER 2 代码实现

1.我们维护一个优先队列,每次只取出高度最高的点进行操作(因为只有最高点的高度可能变化,所以不必担心优先队列里已经排序的数据发生错误)
2.每次从队头取出高度最高的点u,对u连接的每个v尝试推流,如果v不在队中则v入队,再对u的”水库“进行检查,因为我们最后要使每个点的“水库”为0,所以我们需要对u进行重贴标签并再次入队,以使“水库”中的水流出去(注意,推流到汇点的流计入答案,推流到源点的流则作废
3.重复执行2直到队空

CHAPTER 3 优化常数

BFS

在正式开始HLPP之前,我们可以先对原图进行Bfs为所有点划定一个大概的初始高度,这样可以避免反复的高度变化,节省了大量时间

GAP

如果发现没有任何一个点的高度等于某一个值,则说明比这个高度高的所有点均无法推流,这时可以把所有高度比这个值大且比n+1小(源点高度为n)的点的高度都直接设为n+1,以便这些点快速把流退回源点结束算法。
具体地,如果我们在给某个点重贴标签的过程中发现这个点所在的高度没有其他点了,这时我们使用gap优化直接改变一些点的高度,由于“某个点”的高度是队中最高,所以这个优化对优先队列中的数据没有影响

CHAPTER 4 完整代码

#include<bits/stdc++.h>
using namespace std;

#define ll long long 

const int INF = 0x3f3f3f3f;
const ll LLINF = 0x3f3f3f3f3f3f3f3f;
const int N= 1205;
const int M= 120005;

ll x[N];
int n,m,st,ed,h[N],head[N],gap[N*2],edgeid=1;
bool inq[N];

struct cmp
{
    bool operator()(int x,int y) const {return h[x]<h[y];}
};
priority_queue<int,vector<int>,cmp> pq;
queue<int> q;

struct edge{int v,w,nxt;}e[M*2];

inline int Read()
{
    int ans=0;
    bool flag=0;
    char ch=getchar();
    while(!isdigit(ch) && ~ch)
    {
        flag |= (ch=='-');
        ch=getchar();
    }
    while(isdigit(ch) && ~ch)
    {
        ans=(ans<<1)+(ans<<3)+(ch^48);
        ch=getchar();
    }
    if(flag) ans=-ans;
    return ans;
}

inline void addedge(int u,int v,int w)
{
    edgeid++;
    e[edgeid].v=v;
    e[edgeid].w=w;
    e[edgeid].nxt=head[u];
    head[u]=edgeid;
    edgeid++;
    e[edgeid].v=u;
    e[edgeid].w=0;
    e[edgeid].nxt=head[v];
    head[v]=edgeid;
}

bool Bfs()
{
    for(int i=1;i<=n;i++) h[i]=INF;
    h[ed]=0;
    q.push(ed);
    while(!q.empty())
    {
        int u=q.front();q.pop();
        for(int i=head[u];i;i=e[i].nxt) if(e[i^1].w && h[e[i].v]>h[u]+1) h[e[i].v]=h[u]+1,q.push(e[i].v);
    }
    return (h[st]!=INF);
}

void Push(int u)
{
    for(int i=head[u];i;i=e[i].nxt) 
    {
        int v=e[i].v;
        if(e[i].w && h[v]+1==h[u])
        {
            int f;
            f=min((ll)e[i].w,x[u]);
            x[u]-=f,x[v]+=f,e[i].w-=f,e[i^1].w+=f;
            if(v!=st && v!=ed && !inq[v]) pq.push(v),inq[v]=1;
            if(!x[u]) break;
        }
    }
}

void Relabel(int u)
{
    h[u]=INF;
    for(int i=head[u];i;i=e[i].nxt) if(e[i].w && h[e[i].v]+1<h[u]) h[u]=h[e[i].v]+1;
}

void Hlpp()
{
    if(!Bfs()) return ;
    h[st]=n;
    for(int i=0;i<=2*n;i++) gap[i]=0;
    for(int i=1;i<=n;i++) if(h[i]<INF) gap[h[i]]++;
    for(int i=head[st];i;i=e[i].nxt)
    {
        int f,v=e[i].v;
        if((f=e[i].w) && h[v]<INF)
        {
            e[i].w-=f;
            e[i^1].w+=f;
            x[st]-=f;
            x[v]+=f;
            if(v!=st && v!=ed && !inq[v]) pq.push(v),inq[v]=1;
        }
    }
    while(!pq.empty())
    {
        int u=pq.top();
        inq[u]=0;
        pq.pop();
        Push(u);
        if(x[u])
        {
            gap[h[u]]--;
            if(!gap[h[u]]) for(int i=1;i<=n;i++) if(i!=st && i!=ed && h[i]>h[u] && h[i]<n+1) h[i]=n+1;
            Relabel(u);
            gap[h[u]]++;
            pq.push(u); 
            inq[u]=1;
        }
    }
}

signed main()
{
    // freopen("working.in","r",stdin);
    n=Read();m=Read();st=Read();ed=Read();
    for(int i=1;i<=m;i++)
    {
        int u,v,w;
        u=Read();v=Read();w=Read();
        addedge(u,v,w);
    }
    Hlpp();
    cout<<x[ed];
    return 0;
}

CHAPTER 5 重要细节

GAP

点的最大高度可能达到\(2n-1\),gap优化数组的大小要开\(2n\)

无脑入队

由于我们对源点的推流是特殊处理的,即对于跟源点相连的点会有无脑入队的情况。如果恰好这时候被入队的点与汇点之间没有路径,这个点的高度没有被更新过(INF),再进行gap优化就会RE 所以我们在处理源点入队要加一个限制条件,即h[i]<INF

爆int

即使这套题的数据范围看起来处处表明这题不用开long long,但由于预流推进执行过程中流可能不合法,即可能会有点的“水库”存了超过int范围的流,所以要对存每个点“水库”大小的数组开long long

posted @ 2024-02-16 14:45  yeyou26  阅读(30)  评论(0编辑  收藏  举报