从通用预流推进到先进先出预流推进、HLPP、EXCESS SCALING

通用预留推进设计

网络流的定义

对于一张网络我们需要将初始容量为 0 0 0 的反向边也在加入边时进行添加,并依据斜对称性在推送后进行修改,对于剩余容量为 0 0 0的边忽略判断连通性。

然后我们定义进入结点的流超过流出结点的流的剩余部分为超额流,即堵上的。

再后我们定义高度限制流动方向。

那么在推送非超额流后对于超额流我们依据高度向低 1 1 1处推送。

无法推送时,则将其高度提升至最低连通点 + 1 +1 +1

依次规则直到全部没有超额流。

这是最通用的预留推进算法。

#include <iostream>
#include <queue>

using namespace std;

#define int long long

const int inf=1<<30;
int n,m,s,t;
struct edge
{
    int to,val,next;
}e[1000005];
int cnt=1,head[1000005];

void add(int x,int y,int z)
{
    cnt++;
    e[cnt].to=y;
    e[cnt].val=z;
    e[cnt].next=head[x];
    head[x]=cnt;
}

int h[1000005],w[1000005],vis[1000005];
queue<int>q;

int relabel(int x)
{
    int minn=inf;
    for(int i=head[x];i;i=e[i].next)
    {
        int to=e[i].to;
        if(e[i].val)
        {
            minn=min(minn,h[to]);
        }
    }
    if(minn==inf)
    {
        return 0;
    }
    h[x]=minn+1;
    return 1;
}

void push_(int x,int i)
{
    int flow=min(e[i].val,w[x]);
    e[i].val-=flow;
    e[i^1].val+=flow;
    w[x]-=flow;
    w[e[i].to]+=flow;
}

int push_relabel()
{
    h[s]=n;
    for(int i=head[s];i;i=e[i].next)
    {
        int to=e[i].to,val=e[i].val;
        if(val)
        {
            e[i].val-=val;
            e[i^1].val+=val;
            w[s]-=val;
            w[to]+=val;
            if(vis[to]==0&&to!=s&&to!=t&&relabel(to))
            {
                q.push(to);
                vis[to]=1;
            }
        }
    }
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        vis[x]=0;
        for(int i=head[x];i;i=e[i].next)
        {
            int v=e[i].to;
            if(w[x]==0)break;
            if(e[i].val==0)continue;
            if(h[x]==h[v]+1)
            {
                push_(x,i);
            }
            if(w[v]!=0&&v!=s&&v!=t&&vis[v]==0&&relabel(v))
            {
                q.push(v);
                vis[v]=1;
            }
        }
        if(w[x]&&vis[x]==0&&relabel(x))
        {
            vis[x]=1;
            q.push(x);
        }
    }
    return w[t];
}

signed main()
{
    int i,j,k;
    cin>>n>>m>>s>>t;
    for(i=1;i<=m;i++)
    {
        int x,y,z;
        cin>>x>>y>>z;
        add(x,y,z);
        add(y,x,0);
    }
    cout<<push_relabel();
    return 0;
}

LOJ 良好

洛谷 良好

UOJ 不好

但是在大数据下还是远远快于 D i n i c \sf Dinic Dinic 甚至 I S A P \sf ISAP ISAP

虽然时间复杂度为 O ( n 2 m ) O(n^2m) O(n2m) 但实际有时比 D i n i c \sf Dinic Dinic 快得多。

二分图 洛谷 比较危险

通用预流推进实际运用尝试

我们寻找一些 Dinic 无法轻易通过的题目测试通用预流推进有多强。

看到 P2057,毫无悬念的通过了。

然后我们看到了 P1361。

只有 44 44 44 分,对于最大数据范围需要跑 60 60 60 秒。

从建模角度来看, s s s t t t 最多四步,预处理了层次就是Dinic 在层数较低时更快的原因。

所以我们也需要预处理层次,不过在预流推进中是高度。

BFS优化

我们使用 BFS 将 h u h_u hu 初始化为 u u u t t t 的最短距离。

特殊的 h s = n h_s=n hs=n

同时也能检查连通性排除无解。

UOJ 效果不是很显著

以下的预流推进算法自带此优化不再赘述。

#include <iostream>
#include <queue>
#include <cstring>

using namespace std;

#define int long long

const int inf=1<<30;
int n,m,s,t;
struct edge
{
    int to,val,next;
}e[1000005];
int cnt=1,head[1000005];

void add(int x,int y,int z)
{
    cnt++;
    e[cnt].to=y;
    e[cnt].val=z;
    e[cnt].next=head[x];
    head[x]=cnt;
}

int h[1000005],w[1000005],vis[1000005],cnth[1000005],book[1000005];
queue<int>q;
queue<int>qq;
void bfs()
{
    memset(h,0x3f,sizeof(h));
    h[t]=0;
    qq.push(t);
    while(!qq.empty())
    {
        int u=qq.front();
        qq.pop();
        for(int i=head[u];i;i=e[i].next)
        {
            int v=e[i].to;
            if(e[i^1].val&&h[u]+1<h[v])
            {
                h[v]=h[u]+1;
                if(book[v]==0)
                {
                    qq.push(v);
                    book[v]=1;
                }
            }
        }
    }
}

int relabel(int x)
{
    int minn=inf;
    for(int i=head[x];i;i=e[i].next)
    {
        int to=e[i].to;
        if(e[i].val)
        {
            minn=min(minn,h[to]);
        }
    }
    if(minn==inf)
    {
        return 0;
    }
    h[x]=minn+1;
    return 1;
}

void push_(int x,int i)
{
    int flow=min(e[i].val,w[x]);
    e[i].val-=flow;
    e[i^1].val+=flow;
    w[x]-=flow;
    w[e[i].to]+=flow;
}

int push_relabel()
{
    bfs();
    h[s]=n;
    for(int i=head[s];i;i=e[i].next)
    {
        int to=e[i].to,val=e[i].val;
        if(val)
        {
            e[i].val-=val;
            e[i^1].val+=val;
            w[s]-=val;
            w[to]+=val;
            if(vis[to]==0&&to!=s&&to!=t&&relabel(to))
            {
                q.push(to);
                vis[to]=1;
            }
        }
    }
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        vis[x]=0;
        for(int i=head[x];i;i=e[i].next)
        {
            int v=e[i].to;
            if(w[x]==0)break;
            if(e[i].val==0)continue;
            if(h[x]==h[v]+1)push_(x,i);
            if(w[v]!=0&&v!=s&&v!=t&&vis[v]==0&&relabel(v))
            {
                q.push(v);
                vis[v]=1;
            }
        }
        if(w[x]&&vis[x]==0&&relabel(x))
        {
            vis[x]=1;
            q.push(x);
        }
    }
    return w[t];
}

signed main()
{
    int i,j,k;
    cin>>n>>m>>s>>t;
    for(i=1;i<=m;i++)
    {
        int x,y,z;
        cin>>x>>y>>z;
        add(x,y,z);
        add(y,x,0);
    }
    cout<<push_relabel();
    return 0;
}

先入先出预流推进算法

还是不够快。

你或许认为我们不得不去面对 HPLL了。

但实际上这中间还夹着一个 O ( n 3 ) O(n^3) O(n3) 的名为 FIFOPP 算法。

非常简单,只需要将上方的通用预流推进算法中的队列改为栈即可跑的飞起。

至于为啥。

自行查看论文。

#include <iostream>
#include <queue>
#include <cstring>
#include <stack>

using namespace std;

#define int long long

const int inf=1<<30;
int n,m,s,t;
struct edge
{
    int to,val,next;
}e[1000005];
int cnt=1,head[1000005];

void add(int x,int y,int z)
{
    cnt++;
    e[cnt].to=y;
    e[cnt].val=z;
    e[cnt].next=head[x];
    head[x]=cnt;
}

int h[1000005],w[1000005],vis[1000005],cnth[1000005],book[1000005];
stack<int>q;
queue<int>qq;
void bfs()
{
    memset(h,0x3f,sizeof(h));
    h[t]=0;
    qq.push(t);
    while(!qq.empty())
    {
        int u=qq.front();
        qq.pop();
        for(int i=head[u];i;i=e[i].next)
        {
            int v=e[i].to;
            if(e[i^1].val&&h[u]+1<h[v])
            {
                h[v]=h[u]+1;
                if(book[v]==0)
                {
                    q.push(v);
                    book[v]=1;
                }
            }
        }
    }
}

int relabel(int x)
{
    int minn=inf;
    for(int i=head[x];i;i=e[i].next)
    {
        int to=e[i].to;
        if(e[i].val)
        {
            minn=min(minn,h[to]);
        }
    }
    if(minn==inf)
    {
        return 0;
    }
    h[x]=minn+1;
    return 1;
}

void push_(int x,int i)
{
    int flow=min(e[i].val,w[x]);
    e[i].val-=flow;
    e[i^1].val+=flow;
    w[x]-=flow;
    w[e[i].to]+=flow;
}

int push_relabel()
{
    h[s]=n;
    for(int i=head[s];i;i=e[i].next)
    {
        int to=e[i].to,val=e[i].val;
        if(val)
        {
            e[i].val-=val;
            e[i^1].val+=val;
            w[s]-=val;
            w[to]+=val;
            if(vis[to]==0&&to!=s&&to!=t&&relabel(to))
            {
                q.push(to);
                vis[to]=1;
            }
        }
    }
    while(!q.empty())
    {
        int x=q.top();
        q.pop();
        vis[x]=0;
        for(int i=head[x];i;i=e[i].next)
        {
            int v=e[i].to;
            if(w[x]==0)break;
            if(e[i].val==0)continue;
            if(h[x]==h[v]+1)push_(x,i);
            if(w[v]!=0&&v!=s&&v!=t&&vis[v]==0&&relabel(v))
            {
                q.push(v);
                vis[v]=1;
            }
        }
        if(w[x]&&vis[x]==0&&relabel(x))
        {
            vis[x]=1;
            q.push(x);
        }
    }
    return w[t];
}

signed main()
{
    int i,j,k;
    cin>>n>>m>>s>>t;
    for(i=1;i<=m;i++)
    {
        int x,y,z;
        cin>>x>>y>>z;
        add(x,y,z);
        add(y,x,0);
    }
    cout<<push_relabel();
    return 0;
}

UOJ 极大的提升

最高标号预流推进算法

这里正式介绍 HLPP 算法。

最高标号预流推进算法,顾名思义用优先队列代替队列每次取超额流最大的点推送。

然后依然是进行通用预流推进。

同时还是带上 BFS优化。

UOJ 效果十分明显 (HLPP)

UOJ 又多过一个点 (HLPP-BFS)

#include <iostream>
#include <queue>
#include <cstring>

using namespace std;

#define int long long

const int inf=1<<30;
int n,m,s,t;
struct edge
{
    int to,val,next;
}e[1000005];
int cnt=1,head[1000005];

void add(int x,int y,int z)
{
    cnt++;
    e[cnt].to=y;
    e[cnt].val=z;
    e[cnt].next=head[x];
    head[x]=cnt;
}

int h[1000005],w[1000005],vis[1000005],cnth[1000005],book[1000005];
struct cmp
{
    inline bool operator ()(int a,int b) const
    {
        return h[a]<h[b];
    }
};
priority_queue<int,vector<int>,cmp>q;
queue<int>qq;
void bfs()
{
    memset(h,0x3f,sizeof(h));
    h[t]=0;
    qq.push(t);
    while(!qq.empty())
    {
        int u=qq.front();
        qq.pop();
        for(int i=head[u];i;i=e[i].next)
        {
            int v=e[i].to;
            if(e[i^1].val&&h[u]+1<h[v])
            {
                h[v]=h[u]+1;
                if(book[v]==0)
                {
                    qq.push(v);
                    book[v]=1;
                }
            }
        }
    }
}

int relabel(int x)
{
    int minn=inf;
    for(int i=head[x];i;i=e[i].next)
    {
        int to=e[i].to;
        if(e[i].val)
        {
            minn=min(minn,h[to]);
        }
    }
    if(minn==inf)
    {
        return 0;
    }
    h[x]=minn+1;
    return 1;
}

void push_(int x,int i)
{
    int flow=min(e[i].val,w[x]);
    e[i].val-=flow;
    e[i^1].val+=flow;
    w[x]-=flow;
    w[e[i].to]+=flow;
}

int push_relabel()
{
    bfs();
    h[s]=n;
    for(int i=head[s];i;i=e[i].next)
    {
        int to=e[i].to,val=e[i].val;
        if(val)
        {
            e[i].val-=val;
            e[i^1].val+=val;
            w[s]-=val;
            w[to]+=val;
            if(vis[to]==0&&to!=s&&to!=t&&relabel(to))
            {
                q.push(to);
                vis[to]=1;
            }
        }
    }
    while(!q.empty())
    {
        int x=q.top();
        q.pop();
        vis[x]=0;
        for(int i=head[x];i;i=e[i].next)
        {
            int v=e[i].to;
            if(w[x]==0)break;
            if(e[i].val==0)continue;
            if(h[x]==h[v]+1)push_(x,i);
            if(w[v]!=0&&v!=s&&v!=t&&vis[v]==0&&relabel(v))
            {
                q.push(v);
                vis[v]=1;
            }
        }
        if(w[x]&&vis[x]==0&&relabel(x))
        {
            vis[x]=1;
            q.push(x);
        }
    }
    return w[t];
}

signed main()
{
    int i,j,k;
    cin>>n>>m>>s>>t;
    for(i=1;i<=m;i++)
    {
        int x,y,z;
        cin>>x>>y>>z;
        add(x,y,z);
        add(y,x,0);
    }
    cout<<push_relabel();
    return 0;
}

虽然快了 20 20 20 秒。

可是我们还是无法通过上题。

GAP 优化 HLPP

g a p    n . \sf gap \ \ n. gap  n. 缺口 缺口 缺口

当不存在 u u u 使 h ( u ) = k h(u)=k h(u)=k 那么使 h ( u ) > k h(u)>k h(u)>k u u u h ( u ) = n + 1 h(u)=n+1 h(u)=n+1

即高度出现缺口,将其迅速推回,节省大量的对高度重定的操作。

同时因为可以迅速推回,所以对每个操作都进行相关的优化,存在大量细节问题,如日报中的HLPP遇到不连通的点会RE。

UOJ HLPP(BFS+GAP)

洛谷 HLPP(BFS+GAP)

应该是完美的模板:

#include <iostream>
#include <queue>
#include <cstring>

using namespace std;

#define int long long

const int inf=1<<30;
int n,m,s,t;
struct edge
{
    int to,val,next;
}e[1000005];
int cnt=1,head[1000005];

void add(int x,int y,int z)
{
    cnt++;
    e[cnt].to=y;
    e[cnt].val=z;
    e[cnt].next=head[x];
    head[x]=cnt;
}

int h[1000005],w[1000005],vis[1000005],cnth[1000005],book[1000005];
struct cmp
{
    inline bool operator ()(int a,int b) const
    {
        return h[a]<h[b];
    }
};
priority_queue<int,vector<int>,cmp>q;
queue<int>qq;
void bfs()
{
    memset(h,0x3f,sizeof(h));
    h[t]=0;
    qq.push(t);
    while(!qq.empty())
    {
        int u=qq.front();
        qq.pop();
        for(int i=head[u];i;i=e[i].next)
        {
            int v=e[i].to;
            if(e[i^1].val&&h[u]+1<h[v])
            {
                h[v]=h[u]+1;
                if(book[v]==0)
                {
                    qq.push(v);
                    book[v]=1;
                }
            }
        }
    }
}

void relabel(int x)
{
    h[x]=inf;
    for(int i=head[x];i;i=e[i].next)
    {
        int to=e[i].to;
        if(e[i].val)
        {
            h[x]=min(h[x],h[to]+1);
        }
    }
}

void push_(int x,int i)
{
    int flow=min(e[i].val,w[x]);
    e[i].val-=flow;
    e[i^1].val+=flow;
    w[x]-=flow;
    w[e[i].to]+=flow;
}

int push_relabel()
{
    bfs();
    if(h[s]==0x3f3f3f)return 0;
    h[s]=n;
    for(int i=1;i<=n;i++)
    {
        if(h[i]<0x3f3f3f)
        {
            cnth[h[i]]++;
        }
    }
    for(int i=head[s];i;i=e[i].next)
    {
        int to=e[i].to,val=e[i].val;
        if(val)
        {
            e[i].val-=val;
            e[i^1].val+=val;
            w[s]-=val;
            w[to]+=val;
            if(vis[to]==0&&to!=s&&to!=t)
            {
                q.push(to);
                vis[to]=1;
            }
        }
    }
    while(!q.empty())
    {
        int x=q.top();
        q.pop();
        vis[x]=0;
        for(int i=head[x];i;i=e[i].next)
        {
            int v=e[i].to;
            if(e[i].val&&h[x]==h[v]+1)
            {
                push_(x,i);
                if(v!=s&&v!=t&&vis[v]==0)
                {
                    q.push(v);
                    vis[v]=1;
                }
            }
            if(w[x]==0)i=0;
        }
        if(w[x]&&h[x]<=n+1)
        {
            cnth[h[x]]--;
            if(cnth[h[x]]==0)
            {
                for(int i=1;i<=n;i++)
                {
                    if(h[i]>h[x]&&i!=s&&i!=t&&h[i]<n+1)
                    {
                        h[i]=n+1;
                    }
                }
            }
            relabel(x);
            cnth[h[x]]++;
            vis[x]=1;
            q.push(x);
        }
    }
    return w[t];
}

signed main()
{
    int i,j,k;
    cin>>n>>m>>s>>t;
    for(i=1;i<=m;i++)
    {
        int x,y,z;
        cin>>x>>y>>z;
        add(x,y,z);
        add(y,x,0);
    }
    cout<<push_relabel();
    return 0;
}

值得注意的是带 GAP 优化的 HLPP 会遍历点,所以在建模后更改 n n n 的数值,其余则不需要。

成功的通过了上题

GAP 优化其它

对于通用预流推进优先队列改回队列即可

洛谷 有用但不多

UOJ 有用但不多

对于 FIFOPP

将上代码中的优先队列改为栈即可。

常数会更优一些,因此具有使用价值

UOJ 只慢一点

洛谷 它甚至更快一些

节点选择改进尝试

对于通用的预流推进算法。

队列存储溢出节点并不是必须的。

可以发现以上两种改进均是对溢出节点的挑选方式进行的调整。

那我们试试挑选超额流最大的节点推进。

UOJ 只慢一点

洛谷 未能通过

我们也可以将优先队列的排列方式改为按照 e i + h i e_i+h_i ei+hi

UOJ 还是慢一点

所以各种存储排列方式预流推进实质上是对选择点的改进。

EXCESS SCALING

论文


我的理解:

我们进行 log ⁡ U \log U logU 次 Scaling,每次不断选择 Δ / 2 ≤ e i \Delta/2\leq e_i Δ/2ei h i h_i hi 最小的进行推一次流,更新当前弧,推不了重贴,重置当前弧,保证推到的点的超额流之后仍 < Δ <\Delta <Δ,直到没有 Δ / 2 ≤ e i \Delta/2\leq e_i Δ/2ei,然后 Δ \Delta Δ 减半进入下一轮。


来自CHAT-GPT:

EXCESS SCALING 算法的流程如下:

预流推进初始化:将源点 s s s 的所有边设定为满载流量,源点 s s s h h h 值设定为节点数 n n n,汇点 t t t h h h 值为 0 0 0。同时将 s s s 的所有出边加入到高度为 1 1 1 的桶中。

excess flow scaling:每次推进时限定每条边所能流入的流量为 f ≤ 2 k f \le 2^k f2k,其中 k k k 是一个不断减小的指数。初始值为 k = ⌊ log ⁡ C ⌋ k = \lfloor \log C \rfloor k=logC,其中 C C C 是图中边权的最大值。当 k < 0 k < 0 k<0 时,停止算法。

桶排操作:按照节点的高度 h h h,将所有节点放入相应的高度桶中。同时建立超额流桶,将超额流的节点放入其中。

推进流:从高度最大的桶开始,取出其中的节点 u u u。如果节点 u u u 的超额流 e u e_u eu 大于 0 0 0,则将其推进至相邻节点 v v v。如果 v v v 的高度 h v < h u h_v < h_u hv<hu,则 u u u 将超额流 e u e_u eu 推进至 v v v,更新 e u e_u eu e v e_v ev。如果 v v v 的超额流 e v e_v ev 大于 0 0 0,则将其加入超额流桶中。

重贴标签:如果 u u u 的超额流 e u e_u eu 发生变化,使其超额流不为 0 0 0,则将其高度 h u h_u hu 置为最小的与其相邻的节点的高度加 1 1 1

当高度为 h s > n h_s > n hs>n 时,停止算法。此时, t t t 的超额流大小即为最大流。

在 EXCESS SCALING 算法中, k k k 的初始值是 ⌊ log ⁡ 2 C ⌋ \lfloor \log_2 C \rfloor log2C,其中 C C C 是图中边的最大容量。每次执行 excess flow scaling 阶段时, k k k 的值会减小,一般是 k ← ⌊ k / 2 ⌋ k \leftarrow \lfloor k/2 \rfloor kk/2,直到 k = 0 k=0 k=0。这样做的原因是随着算法的进行,残量网络中的流量会逐渐减小,因此可以逐步减小 k k k 的值,以加快算法的收敛速度。


#include <iostream>
#include <queue>
#include <cstring>
#include <cmath>
#include <cctype>
#include <cstdio>
#include <cstring>

#define int long long

using namespace std;

inline int read()
{
    int x=0;
    char c=getchar();
    while(!isdigit(c))c=getchar();
    while(isdigit(c))x=x*10+c-'0',c=getchar();
    return x;
}

int K,level;
const int inf=(long long)1<<40;
int n,m,s,t;
struct edge
{
    int to,val,next;
}e[1000005];
int cnt=1,head[1000005],now[1000005];

void add(int x,int y,int z)
{
    cnt++;
    e[cnt].to=y;
    e[cnt].val=z;
    e[cnt].next=head[x];
    now[x]=head[x]=cnt;
}

int h[12005],w[12005],cnth[12005];
queue<int>list[12005];
queue<int>qq;
int book[12005];
void bfs()
{
    memset(h,0x3f,sizeof(h));
    h[t]=0;
    qq.push(t);
    while(!qq.empty())
    {
        int u=qq.front();
        qq.pop();
        for(int i=head[u];i;i=e[i].next)
        {
            int v=e[i].to;
            if(e[i^1].val&&h[u]+1<h[v])
            {
                h[v]=h[u]+1;
                if(book[v]==0)
                {
                    qq.push(v);
                    book[v]=1;
                }
            }
        }
    }
}



void push_relabel(int x,int Delta)
{
    int i;
    int found=0;
    for(i=now[x];i;i=e[i].next)
    {
        int v=e[i].to;
        if(h[x]==h[v]+1&&e[i].val)
        {
            found=1;
            break;
        }
        else
        {
            now[x]=e[i].next;
        }
    }
    if(found==1)
    {
        int v=e[i].to;
        int flow=min(e[i].val,w[x]);
        if(v!=t)flow=min(e[i].val,Delta-w[v]);
        e[i].val-=flow;
        e[i^1].val+=flow;
        w[x]-=flow;
        w[v]+=flow;
        if(w[x]<=Delta/2)list[h[x]].pop();
        if(w[v]>Delta/2&&v!=s&&v!=t)
        {
            list[h[v]].push(v);
            level--;
        }
    }
    else
    {
        list[h[x]].pop();
        cnth[h[x]]--;
        if(cnth[h[x]]==0)
        {
            for(int i=1;i<=n;i++)
            {
                if(h[i]>h[x]&&i!=s&&i!=t&&h[i]<n+1)
                {
                    h[i]=n+1;
                }
            }
        }
        int minn=inf;
        for(int i=head[x];i;i=e[i].next)
        {
            int to=e[i].to;
            if(e[i].val)
            {
                minn=min(minn,h[to]);
            }
        }
        h[x]=minn+1;
        if(minn==inf)h[x]=n+1;
        cnth[h[x]]++;
        list[h[x]].push(x);
        now[x]=head[x];
    }
}

int Excess_Scaling()
{
    bfs();
    if(h[s]==0x3f3f3f)return 0;
    h[s]=n;
    for(int i=1;i<=n;i++)
    {
        if(h[i]<0x3f3f3f)
        {
            cnth[h[i]]++;
        }
    }
    for(int i=head[s];i;i=e[i].next)
    {
        int to=e[i].to,val=e[i].val;
        if(val)
        {
            e[i].val-=val;
            e[i^1].val+=val;
            w[s]-=val;
            w[to]+=val;
        }
    }
    for(int k=1;k<=K;k++)
    {
        int Delta=(long long)1<<(K-k);
        for(int i=1;i<=n;i++)
        {
            if(w[i]>Delta/2)
            {
                list[h[i]].push(i);
            }
        }
        level=1;
        while(level<=n)
        {
            if(list[level].empty())level++;
            else
            {
                int x=list[level].front();
                push_relabel(x,Delta);
            }
        }
        for(int i=1;i<=32;i++)
        {
            while(!list[i].empty())
            {
                list[i].pop();
            }
        }
    }
    return w[t];
}


signed main()
{
    int i,j,k;
    n=read();
    m=read();
    s=read();
    t=read();
    for(i=1;i<=m;i++)
    {
        int x=read(),y=read(),z=read();
        K=max(K,(int)log2(z-1)+1);
        add(x,y,z);
        add(y,x,0);
    }
    K++;
    cout<<Excess_Scaling();
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值