预流推进的几种算法
前言
本文介绍几个比较优秀的预流推进算法,包括朴素的ISAP算法,HLPP算法和自己脑补的LCT维护ISAP的算法。
警告:各种网络流算法的复杂度及其退化的可能性
Dinic算法及朴素的ISAP算法:
队列维护的的ISAP算法:
HLPP算法:
如果两者都是那就是
LCT维护的ISAP算法:
前置技能:
网络流的基本定义,Dinic算法,LCT(如果要看LCT维护ISAP的算法的话)。
朴素的ISAP算法
我们首先回忆一下Dinic算法的做法:每次从源点出发bfs出一个层次图,然后只在这个层次图上跑流。
而ISAP沿用了这种思想,但不同之处就是ISAP改为从汇点出发bfs出层次图,接下来动态维护这个层次图。
具体来说,ISAP首先bfs出每一个点和汇点的距离,寻找增广路时只沿着满足
复杂度证明:
首先,每一个点最多被重标号
对于一次“完全推流”,我们知道,这条边
而对于一次增广,至少有一次“完全推流”,所以增广的次数也是
ISAP的几个优化:
1、当前弧/允许弧优化:
对于每一个点用领接表(开O2的话可以用vector)存它的允许弧,这样不用多余地访问不允许推流的边,而允许弧改变当且仅当一个点被重标号了,所以每次重标号的时候修改一下,这样是
2、GAP优化:
开一个桶存每一个高度有几个点,重标号的时候看一下这个高度是否已经没有点了,如果是的话就不用再流了,因为这意味着图出现了断层。
代码:
(暂无,其实大家没必要去写朴素的ISAP,快来写下面的HLPP吧)
HLPP算法
施工中。。。(等我期末考完吧)
代码:
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<map>
using namespace std;
#define re register
#define cmin(a,b) (a>(b)?a=(b):0)
#define cmax(a,b) (a<(b)?a=(b):0)
#define dmin(a,b) ((a)<(b)?(a):(b))
const int inf=1<<30,MaxN=10100,MaxM=121000;
struct edge{int to,v;edge*nx,*rec,*nx1;}e[MaxM<<1];edge*last[MaxN],*cur[MaxN],*cnt=e;
int h[MaxN],gap[MaxN],q[MaxN],n,s,t,n1,g[MaxN],nd[MaxN];
long long w[MaxN];
inline void addE(re int a,re int b,re int c)
{
*++cnt=(edge){b,c,last[a]};last[a]=cnt;cnt->rec=cnt+1;
*++cnt=(edge){a,0,last[b]};last[b]=cnt;cnt->rec=cnt-1;
}
inline void addV(re int a,re int h){nd[a]=g[h];g[h]=a;}
void bfs()
{
re int x,ta=2;q[1]=t;
memset(h,0,(n+1)<<2);h[t]=1;
for(re int i=1;i<ta;i++)
{
x=q[i];if(x==s)continue;
for(re edge*j=last[x];j;j=j->nx)if(!h[j->to])
h[j->to]=h[x]+1,q[ta++]=j->to;
}
}
void hlpp()
{
bfs();h[s]=n+1;re int ta=0,x,d;memset(w,0,(n+1)<<2);
for(re int i=1;i<=n;i++)
{
for(re edge*j=last[i];j;j=j->nx)if(h[j->to]==h[i]-1)
j->nx1=cur[i],cur[i]=j;
gap[h[i]]++;
}
for(re edge*j=last[s];j;j=j->nx)if(j->v&&h[j->to])
addV(j->to,h[j->to]),cmax(ta,h[j->to]),
w[j->to]+=j->v,j->rec->v=j->v,j->v=0;
while(ta)
{
x=g[ta];g[ta]=nd[x];nd[x]=0;
if(h[x]>n)continue;
for(re edge*&j=cur[x];j&&w[x];j=j->nx1)//推流
if(j->v&&h[j->to]==h[x]-1){
d=dmin(j->v,w[x]);if(!w[j->to]&&j->to!=t)
addV(j->to,h[j->to]),cmax(ta,h[j->to]);
j->v-=d,j->rec->v+=d;w[x]-=d;w[j->to]+=d;
if(!w[x])break;
}
if(w[x])//重标号
{
gap[h[x]]--;
if(!gap[h[x]])//gap优化
{
for(re int i=1;i<=n;i++)if(h[i]>h[x])gap[h[i]]=0,h[i]=n+1;h[x]=n+1;
while(ta&&!g[ta])ta--;continue;
}
h[x]=n+1;
for(re edge*j=last[x];j;j=j->nx)
if(j->v)cmin(h[x],h[j->to]+1);gap[h[x]]++;
for(re edge*j=last[x];j;j=j->nx)
if(j->v)if(j->v&&h[j->to]==h[x]-1)j->nx1=cur[x],cur[x]=j;
else if(j->rec->v&&h[j->to]==h[x]+1)j->rec->nx1=cur[j->to],cur[j->to]=j->rec;
addV(x,h[x]),cmax(ta,h[x]);
}while(ta&&!g[ta])ta--;
}
}
int main()
{
re int m,x,y,c;
scanf("%d%d%d%d",&n,&m,&s,&t);
for(re int i=1;i<=m;i++)scanf("%d%d%d",&x,&y,&c),addE(x,y,c);
hlpp();
printf("%lld\n",w[t]);
}
LCT维护ISAP(口胡警告!!)
前言
可能因为常数太大,太不实用了,我从来在网上没见过这个算法。只有维基上提了一下(只讲了复杂度,没有讲做法),于是我花了几天时间把它给脑补出来,也没有自己实现过。所以可能会有问题,有问题请帮忙指出一下。
正文
让我们再回忆一下关于ISAP算法的复杂度,我们发现大部分时间都花在增广上面,而增广的实质是在找到一条路径的最小边权
这样暴力做是
有!LCT!
我们从汇点以外所有点取出一条允许弧建成一颗树(这样做显然会建成树),然后我们的操作就变成了:
1、找到源点
2、源点
3、删除(cut掉)所有链上边权为
4、对于所有“被删边的点”找到下一条允许弧link上了(这样操作完显然显然还会是树)。
5、若有点找不到“允许弧”,则重标号,cut掉所有指向它的边并执行步骤4。
(事实上3、4、5是同时执行的)
6、如果有一个点找不到“允许弧”,直接删除这个的并cut掉所有指向它的边,然后执行步骤4。
7、当图出现断层时,终止操作。
根据我在上面ISAP部分的证明,link和cut的次数都是
关于反向边的处理:
显然地,一条边和它的反向边不可能同时为“允许弧”,所以我们只需要在cut掉一条边的时候再处理反向边就好了。
代码:
(暂无,可能得等挺久才会来填这个坑的吧。。。)