空间 O(n) 树上倍增
没有用的小技巧。
基本被树剖偏序了,唯一的应用场景是它可以支持动态加叶子。
预处理 \(O(n)\),单次查询 \(k\) 级祖先/\(\text{LCA}\) 为 \(O(\log n)\),可以证明它的跳跃次数是上界是 \(3\log n\),因此查询理论常数比较高。
实际上由于低空间常数和预处理复杂度,它竟然跑得比普通树上倍增快的多!但它略慢于树剖。
#include <cstdio>
#include <algorithm>
using namespace std;
int read(){/*...*/}
const int N=500003;
int n,m,rt;
int hd[N],ver[N<<1],nxt[N<<1],tot;
void add(int u,int v){
nxt[++tot]=hd[u];hd[u]=tot;ver[tot]=v;
}
int jump[N],ft[N],de[N];
void dfs(int u,int fa){
ft[u]=fa;
int jf=jump[fa];
int jjf=jump[jf];
if(fa&&jf&&jjf&&de[jf]-de[jjf]==de[fa]-de[jf]) jump[u]=jjf;
else jump[u]=fa;
for(int i=hd[u];i;i=nxt[i]){
int v=ver[i];
if(v==fa) continue;
de[v]=de[u]+1;
dfs(v,u);
}
}
int lca(int u,int v){
if(de[u]<de[v]) swap(u,v);
while(de[u]>de[v])
if(de[jump[u]]<de[v]) u=ft[u];
else u=jump[u];
while(u!=v)
if(jump[u]==jump[v]){u=ft[u];v=ft[v];}
else{u=jump[u];v=jump[v];}
return u;
}
int main(){
n=read();m=read();rt=read();
for(int i=1;i<n;++i){
int u=read(),v=read();
add(u,v);add(v,u);
}
dfs(rt,0);
for(int i=1;i<=m;++i) printf("%d\n",lca(read(),read()));
return 0;
}