空间 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;
}
posted @ 2023-08-17 10:12  yyyyxh  阅读(61)  评论(0编辑  收藏  举报