最近公共祖先

定义

对于两个点,LCA是它们的祖先(或自己)中距离他们最近的点


不妙做法

查询 \(O(n)\)

向上标记

Rt. 一个节点不断往父节点跑,标记节点

另一个节点也是往上跑碰到标记过的就是LCA

向上调整

Rt. 深度深的节点往上调,调到深度一样

如果调成一样了第一个调对的就是LCA,不然往上继续调


树上倍增

预处理 \(O(n\log n)\) 查询 \(O(\log n)\)

珂以理解成向上调整的优化,跟 ST 差不多

预处理

\(f(i,j)\) 表示节点 \(i\)\(2^j\) 辈祖先

那么显然要

\[f(i,j)\gets f(f(i-1,j-1),j-1) \]

记得要初始化 \(f(i,0)\gets fa_i\)

查询

查询 \(x,y\) 的 LCA

不妨令 \(dep_x<dep_y\)

那先把 \(y\) 调到 \(x\) 的深度,之后二进制同步调搞出 LCA 即可

具体而言,就是从 \(\log n\)\(0\) 循环一遍,如果珂以调(仍不到 \(x\) | 调后不一样) 就调

注意特判 \(x,y\) 已经相同的情况,不然跑不出来

实现

#include<bits/stdc++.h>
#define MN 550000

using namespace std;
typedef long long ll;

int n, m;
int u, v;
int dep[MN];
int f[MN][22], t;
vector<int>to[MN]; 

void dfs(int p,int fat) {
	dep[p]=dep[fat]+1, f[p][0]=fat;
	for(int i=0; i<to[p].size(); ++i)
		if(to[p][i]!=fat)
			dfs(to[p][i],p);
}

void pwork() {
	dfs(1,0); t=log2(n);
	for(int i=1; i<=t; ++i)
		for(int p=1; p<=n; ++p)
				f[p][i]=f[f[p][i-1]][i-1];
}

int LCA(int x,int y) {
	if(dep[x]>dep[y]) swap(x,y);
	for(int i=t; i>=0; --i)
		if(dep[f[y][i]]>=dep[x])
			y=f[y][i];
	if(x==y) return x;
	for(int i=t; i>=0; --i)
		if(f[x][i]!=f[y][i])
			x=f[x][i], y=f[y][i];
	if(u==v) return u;
	return f[x][0];
}

int main() {
	scanf("%d%d", &n, &m);
	for(int i=1; i<n; ++i) {
		scanf("%d%d", &u, &v);
		to[u].push_back(v);
		to[v].push_back(u);
	}
	pwork();
	while(m--) {
		scanf("%d%d", &u, &v);
		printf("%d\n", LCA(u,v));
	}
	return 0;
}

Trajan 獭酱

处理 \(O(n+m)\) ,非常优秀,可惜了

离线,珂以理解成向上标记的优化

总之遍历然后自下而上建立并查集

但是这玩意因为带了个并查集所以常数巨大

posted @ 2023-08-12 22:45  Hypoxia571  阅读(13)  评论(0)    收藏  举报