HLPP
它是 Dinic 进阶的思想。
因为 Dinic
的复杂度满足不了本题了,就需要更高级的算法:HLPP
是目前网络流最大流的最先进的算法了(起码目前是的)。
思路
仍要将整个图分层,但这次抽象的分成高度的图,将汇点定义为 $0$ 的高度,所有能到达它的点高度为 $1$,以此类推。具体的操作是将所有的点的高度的初值为最大值,然后不断取最小值,可以达到的最小高度。最后将源点提升到最高的高度(但不是最大值!)
我们知道的是高处的水可以流向低处(常识)。那么我们将源点看成真正的“源泉”,有无穷的水。往下流(限制是每条路的最大流量),如果一个节点的流量用完了,就是说水到了之后,无路可流了,我们就将这个点的高度从新赋成最大值,这样原来能流向它的点,也由于“地势”的变化,流不过去。(简单来说,就是扔掉了这个点。)
我们不难发现一个事实:从高处流下来的水可能继续向下流。所以我们可以先将高处的水能流的全部流完,然后再流低处的水,这样流低处水的时候,“顺带”流走了高出积累下来的水。然后由于地势的提高(被提高地势的节点会越来越多,直到不能流为止。)最后会返回源点。
而汇点能流过来的量就是“最大流量”。
优化:
由于我们根据的高度进行的点的分层,所以会出现一种情况:一层(同一高度)只剩一个点了,但是这个点已经无路可流了,那么它被提高的同时意味着,比它高的所有的点都已经没用了。
代码思路
加边(双向边)
分层(节点设置高度)
统计同一高度节点数量(方便判断上面所说的“优化”)
枚举源点的流程(源点的水是无尽的所以特殊处理)
处理所有节点流量情况(我们可以开一个优先队列,然高度高的先处理)
判断“断层”情况(上面所说的“优化”)
输出最终流向汇点的“量”
代码
注:本篇代码本着“原汁原味”,没有别的题解的玄学优化,所以时间复杂度会差一些。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<string>
#include<cctype>
#include<queue>
#include<vector>
using namespace std;
long long r_r(){//快读
long long x=0,f=1;
char c=getchar();
while(!isdigit(c)){
if(c=='-')f=-1;
c=getchar();
}
while(isdigit(c)){
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
return x*f;
}
const int o_o=4e5+10;
struct po{
int n_t;
int v;//下一个节点
long long l;//流量
}p_p[o_o];
int h_d[o_o],x_p=1;
int n=r_r(),m=r_r(),s=r_r(),t=r_r();
void a_d(int u,int v,long long l){//建边
p_p[++x_p].v=v;
p_p[x_p].l=l;
p_p[x_p].n_t=h_d[u];
h_d[u]=x_p;
}
int h_t[o_o];//节点高度
queue<int>q_q;
long long s_v[o_o];//节点剩余的“量”
int b_b[o_o];//标记节点是否已经遍历过(防止跑环)
int g_p[o_o];//当前高度有多少节点
struct pp{
bool operator()(int a,int b)const{
return h_t[a]<h_t[b];
}
};
priority_queue<int,vector<int>,pp>p_q;
bool b_f(){//分层图
memset(h_t,0x3f,sizeof h_t);//初始化高度数组
h_t[t]=0;//从汇点开始提高度
q_q.push(t);
while(!q_q.empty()){
int k_k=q_q.front();
q_q.pop();
for(int i=h_d[k_k];i;i=p_p[i].n_t){//枚举出边
int v=p_p[i].v;
if(p_p[i^1].l&&h_t[v]>h_t[k_k]+1){//因为是往回找,所以判断反向弧
h_t[v]=h_t[k_k]+1;//更新高度,使高度尽量小
q_q.push(v);//入队
}
}
}
return h_t[s]!=h_t[0];//源点汇点是否联通
}
void x_l(int k){//节点“量”下流
for(int i=h_d[k];i;i=p_p[i].n_t){//枚举出边
int v=p_p[i].v;
if(p_p[i].l&&h_t[v]+1==h_t[k]){//边有流量并且深度是顺着下一层
int a_s=min(s_v[k],p_p[i].l);
p_p[i].l-=a_s;//更新边流量
p_p[i^1].l+=a_s;//更新反向弧流量
s_v[k]-=a_s;//更新节点剩余的“量”
s_v[v]+=a_s;//更新下一节点的“量”
if(v!=s&&v!=t&&!b_b[v]){//流向的不是源点不是汇点并且没遍历过
p_q.push(v);//入队
b_b[v]=1;//标记
}
if(!s_v[k])break;//没有流量了,退出
}
}
return ;
}
void n_h(int k_k){
h_t[k_k]=h_t[0];//将节点初始化为“不可到达”节点
for(int i=h_d[k_k];i;i=p_p[i].n_t){//枚举出边
int v=p_p[i].v;
if(p_p[i].l&&h_t[v]+1<h_t[k_k])//有可以流的边
h_t[k_k]=h_t[v]+1;//更新高度,尽可能的小
}
return ;
}
int main(){
for(int i=1;i<=m;i++){
int a=r_r(),b=r_r();
long long v=r_r();
a_d(a,b,v);//建边
a_d(b,a,0);//建反向弧
}
if(!b_f()){//源点汇点不连通直接返回
puts("0");
return 0;
}
h_t[s]=n+1;//源点提到最高的位置
memset(g_p,0,sizeof g_p);//高度节点数初始化为 0
for(int i=1;i<=n;i++)if(h_t[i]!=h_t[0])g_p[h_t[i]]++;//如果节点高度没有被扔掉,说明可以流到,当前高度节点数量增加 1
//处理源点的流动
for(int i=h_d[s];i;i=p_p[i].n_t){
int a_s=p_p[i].l;
int v=p_p[i].v;
p_p[i].l-=a_s;//更新边流量
p_p[i^1].l+=a_s;//更新反向弧流量
s_v[s]-=a_s;//更新节点剩余的“量”
s_v[v]+=a_s;//更新下一节点的“量”
if(v!=s&&v!=t&&!b_b[v]){//流向的不是源点不是汇点并且没遍历过
p_q.push(v);//入队
b_b[v]=1;//标记
}
}
//处理每个节点
while(!p_q.empty()){
int k_k=p_q.top();
p_q.pop();
b_b[k_k]=0;//删除标记
x_l(k_k);//节点下流
if(s_v[k_k]){//当前节点仍有流量
if(!--g_p[h_t[k_k]])//当前深度没有别的节点
for(int i=1;i<=n;i++)//枚举所有节点
if(i!=s&&i!=t&&h_t[i]>h_t[k_k]&&h_t[i]<n+1)//不为源点和汇点并且比当前节点高度大且没有被扔掉
h_t[i]=n+1;//将可以到达它的节点扔掉
n_h(k_k);//更新节点高度
++g_p[h_t[k_k]];//当前深度的节点增加 1
p_q.push(k_k);//入队
b_b[k_k]=1;//标记
}
}
printf("%d\n",s_v[t]);//输出最终流向汇点的“量”
return 0;
}