LCA转RMQ 方法(预览)
这个方法要开倍增求 \(LCA\) 方法的两倍空间, 还要开额外数组记录深度和 \(dfn\), 各位勇士慎重。
可以 \(O(n + n\log n)\) 预处理 \(O(1)\) 动态回答 \(LCA\) (常数和RMQ一样), 不谈空间缺点的话还是吊打倍增求 \(LCA\) 的。
算法流程:
算法的过程就是在便利有根树的过程中按照访问节点的次序将节点的标号插入一个队列(第一次访问该节点时插一次), 然后在访问完这个节点的某一个子节点时再插一次。
然后将第一次访问该节点时, 将该节点的标号插入队列后队列的 \(size\) 记为 \(fis\)。
可以发现询问两个节点 \(i、j\) 的 \(LCA\) 时, 队列中 \(fis_i\) 到 \(fis_j\) 之间的节点, 深度最小的就是 \(i、j\) 的 \(LCA\)。
具体证明:
假设询问的两个节点为 \(i、j\)
若\(i、j\)不是祖先后代关系。
即以某个节点 \(k\) 为 \(LCA\) 的两个节点 \(i、j\) 分别在 \(k\) 的两颗不同子树内, \(fis_i\) 到 \(fis_j\) 之间会有 \(k\)(见算法流程), 但绝不会有 深度小于 \(k\) 的节点出现(\(k\) 的子树还没访问完, 不可能回溯)。
若 \(i、j\)有祖先后代关系。
不妨假设 \(dep(i) \leq dep(j)\), 其它过程类似于上面的证明。
Luogu的LCA模板AC代码
//再次强调空间是倍增的两倍
//你谷开 st[500005][21] 会T就很玄学(可能是我才疏学浅)
#include<bits/stdc++.h>
using namespace std;
const int maxn = 500015;
int n,m,s;
int ct, hed[maxn], nxt[maxn<<1], ver[maxn<<1];
void ad(int a,int b) {
ver[++ct] = b;
nxt[ct] = hed[a];
hed[a] = ct;
}
int tot, st[22][maxn<<1], fis[maxn], dep[maxn];
void dfs(int x,int fa,int Dep) {
st[0][++tot] = x, fis[x] = tot, dep[x] = Dep;
for(int i=hed[x];i;i=nxt[i]) {
int y = ver[i]; if(y==fa) continue;
dfs(y,x,Dep+1); st[0][++tot] = x;
}
}
int calc(int x,int y) {
return (dep[x]<dep[y]?x:y);
}
int lca(int x,int y) {
int l=fis[x], r=fis[y]; if(l>r) swap(l,r);
int k = log2(r-l+1);
return calc(st[k][l], st[k][r-(1<<k)+1]);
}
int main()
{
scanf("%d%d%d", &n,&m,&s);
for(int i=1;i<=n-1;++i) {
int x,y; scanf("%d%d", &x,&y);
ad(x,y); ad(y,x);
}
dfs(s,0,1);
for(int k=1;k<=20;++k)
for(int i=1;i+(1<<k)-1<=tot; ++i)
st[k][i] = calc(st[k-1][i], st[k-1][i+(1<<(k-1))]);
for(int i=1; i<=m; ++i) {
int x,y; scanf("%d%d", &x,&y);
cout << lca(x,y) << '\n';
}
return 0;
}