LCA,即最近公共祖先,是图论中关于树的一个非常重要的定义。
在一棵树中,若节点Z既是节点X的祖先节点,又是节点Y的祖先节点,那么称节点Z是节点X和Y的公共祖先节点。
两种解法:
- Ans1
首先记录树中每个节点的深度,如果节点和的深度不一样,将深度调整为同一深度(让更深的节点用和浅节点深度相同的它的祖先节点代替),然后从这两个点开始想上扫描整棵树,每次上升单位为1的高度,知道两个节点第一次到达同一个节点,那么这个节点就是两个节点的最近公共祖先。时间复杂度
- Ans2
首先进行预处理,表示节点的辈祖先,如果,那么就是节点的直接祖先,即父亲节点,可以得到一个关系,,自己画一下图就得出来,不作详细说明。类似于一个dp的递推式,可以用两个循环来进行预处理,而且在遍历辈数的时候,只需要遍历到即可,预处理的复杂度为,然后也是记录每个节点的深度,调整连个点的深度一致,对每次循环,采用倍增的思想,首先尝试两个点的辈祖先,是否可行,(从开始递减),如果抵达的点不同,说明未寻到LCA,则让 ,,最后结束的时候,和必定只差一步,所以答案就是,每次询问的复杂度是,总体复杂度是
其实还有第三种方法,可以用tarjan算法优化第一种情况,但是前提条件是需要将询问离线,算法时间复杂度是 的,以后有机会了补上吧。
// LCA O(Nlog(N))
#include <bits/stdc++.h>
#pragma warning (disable:6031)
#define mem(a, b) memset(a, b, sizeof a)
using namespace std;
const int N = 310;
int head[N], nex[N], to[N];
int f[N][N]; //倍增思想,依然用不到N * N的区间,大概是N * (log(N) / log(2))的大小就够了
int cnt;
int d[N];
typedef struct build_tree {
build_tree() {
cnt = 0;
mem(head, -1);
mem(nex, -1);
mem(f, -1);
mem(d, 0);
}
void init() {
build_tree();
}
void add(int x, int y) {
cnt++;
nex[cnt] = head[x];
head[x] = cnt;
to[cnt] = y;
}
}bt;
int n; // 树上n个点,n - 1条边
void dfs(int x, int depth) {
// 计算深度
d[x] = depth;
for (int i = head[x]; i != -1; i = nex[i]) {
int y = to[i];
dfs(y, depth + 1);
}
}
int ans(int x, int y) {
// 查询x和y的lca
if (d[x] < d[y])swap(x, y);
// x 比 y 深
for (int i = log(n) / log(2); i >= 0; i--) {
if (d[f[x][i]] >= d[y])x = f[x][i];
}
if (x == y)return x;
for (int i = log(n) / log(2); i >= 0; i--) {
if (f[x][i] != f[y][i])x = f[x][i], y = f[y][i];
}
return f[x][0];
}
int main()
{
bt t;
t.init();
scanf("%d", &n);
for (int i = 1; i < n; i++) {
int a, b;
scanf("%d %d", &a, &b);
t.add(a, b);
f[b][0] = a;
// 步长为0就是直接祖先,父亲节点
}
int tt = log(n) / log(2);
for (int i = 1; i <= tt; i++) {
for (int j = 1; j <= n; j++) {
f[j][i] = f[f[j][i - 1]][i - 1];
}
}
return 0;
}