约束rmq_约束RMQ

不知道为什么网上找不到太多相关的资料,所以写一个小总结,并附有能用的代码。

约束RMQ,就是RMQ区间必须满足两项之差最大为1,采用ST表的话,这时候有O(n)建表,O(1)查询的优秀复杂度

求LCA,通过DFS把原树转化为深度序列,就等价于求区间最小值 (取到的位置)

由于DFS的性质,该序列两个数之间显然相差1,所以可以使用约束RMQ解决

先总体概括一下做法:把原序列分块,块内预处理,块间做ST表

分块大小定为L=log(n)/2,这样共分D=n/L块,对这D个数(块内最小值)做正常ST表,建表复杂度O(Dlog(D))=O((n/L)(log(n)-log(L))=O(n)

我们要保证每个步骤都是O(n)的,log(n)/2正好消去了ST建表时的log

但在此之前,我们得处理出块内的最小值,该怎么做呢?一个正常想法就是枚举每个数,一共是O(n)复杂度

但是,这样做虽然留下了每块的最小值以及其取到的位置,若考虑查询块的一个区间,而这个区间恰好取不到最小值,这时候只能暴力枚举,就破坏了查询O(1)了

至此我们仍没有使用其±1的特殊性质,现在考虑一下。

块内一共log(n)/2个数,由乘法原理可知,本质不同的块有U=2^(log(n)/2)=n^(1/2)个,我们不妨处理出每个这种块,复杂度Ulog(n)/2,这个函数增长是小于线性的,可以认为是O(n)

这样,处理出每个块内两元素的大小关系,就可以用01唯一表示一个块了,可以用二进制存下来,作为一个块的特征,这一步O(n)

这样有一个好处,即使查询块内一个区间,我们只需要提取这个区间对应的二进制数,就可以在预处理的数组中O(1)查询了

查询时,类似分块,边块直接查表,中间部分ST表查询,查询时O(1)的。

至此我们完成了O(n)建表,O(1)查询的约束RMQ。

同时,对于任何一个序列,可以在O(n)时间内建成一颗笛卡尔树,把查询该序列RMQ转化为求笛卡尔树LCA,就变成O(1)的了。

解决LCA的代码:

//drunk,fix later

#include#include#include

#define Re register

using namespacestd;const int MAXN=1000005;

inlineintrd() {int ret=0,f=1;charc;while(c=getchar(),!isdigit(c))f=c=='-'?-1:1;while(isdigit(c))ret=ret*10+c-'0',c=getchar();return ret*f;

}intn,m,st;structEdge {intnext,to;

} e[MAXN<<1];intecnt,head[MAXN];

inlinevoid add(int x,inty) {

e[++ecnt].next =head[x];

e[ecnt].to=y;

head[x]=ecnt;

}intappear[MAXN],elm[MAXN],dep[MAXN],tot;void dfs(int x,intpre) {

appear[x]=++tot;

elm[tot]=x;

dep[tot]=dep[appear[pre]]+1;for(int i=head[x]; i; i=e[i].next) {int v=e[i].to;if(v==pre) continue;

dfs(v,x);

elm[++tot]=x;

dep[tot]=dep[appear[x]];

}

}intblockLen,num,L[MAXN],R[MAXN],bl[MAXN];int blockTyp[MAXN],f[MAXN][32],g[MAXN][32];intlookUp[MAXN];

inlineint computeType(intx) {int sum=0;for(Re int i=L[x]; i<=R[x]-1; i++)

sum<<=1,sum+=(dep[i+1]>dep[i]);returnsum;

}

inlinevoid calcPos(intx) {int len=0,po=0,cnt=0,mn=1<<30,mnid;

len=blockLen;for(Re int i=len; i>=0; i--) {

po++;if((1<

}

lookUp[x]=mnid-1;

}voidbuild() {

blockLen=log2(tot)/2;

num=tot/blockLen;if(tot%blockLen) num++;for(Re int i=1; i<=num; i++) {

L[i]=(i-1)*blockLen+1;

R[i]=i*blockLen;

}for(Re int i=tot+1; i<=R[num]; i++) dep[i]=(1<<30);for(Re int i=1; i<=tot; i++)bl[i]=(i-1)/blockLen+1;for(Re int i=0; i*i<=tot; i++) calcPos(i);for(Re int i=1; i<=num; i++)blockTyp[i]=computeType(i);for(Re int i=1; i<=num; i++) g[i][0]=(i-1)*blockLen+lookUp[blockTyp[i]],f[i][0]=dep[g[i][0]]; //offset!

for(Re int j=1; (1<

}

inlineint inBlockQuery(int x,inty) {int u=blockTyp[bl[x]],v=(bl[x]-1)*blockLen+lookUp[u];if(x<=v&&v<=y) returnv;int sav=bl[x];

x-=L[sav]-1;y-=L[sav]-1;

u>>=(blockLen-y);

u&=(~((-1)<

}int query(int x,inty) {if(bl[x]==bl[y]) returninBlockQuery(x,y);int mn=1<<30,mnid,tmp;

tmp=inBlockQuery(x,R[bl[x]]);if(dep[tmp]

tmp=inBlockQuery(L[bl[y]],y);if(dep[tmp]0)) len=log2(r-l+1);else returnmnid;if(f[l][len]

}intmain() {

n=rd();m=rd();st=rd();intx,y;for(Re int i=1; i<=n-1; i++) {

x=rd();y=rd();

add(x,y);add(y,x);

}

dfs(st,0);build();for(int i=1; i<=m; i++) {

x=rd();y=rd();if(appear[x]>appear[y]) swap(x,y);

printf("%d\n",elm[query(appear[x],appear[y])]);

}return 0;

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值