思路:

设当前节点为 【树上倍增】最近公共祖先(LCA)_树上倍增【树上倍增】最近公共祖先(LCA)_二叉树_02 代表 【树上倍增】最近公共祖先(LCA)_树上倍增 的第 【树上倍增】最近公共祖先(LCA)_父节点_04 个祖先节点,
显然 【树上倍增】最近公共祖先(LCA)_父节点_05 代表 【树上倍增】最近公共祖先(LCA)_树上倍增 的直接父节点,
【树上倍增】最近公共祖先(LCA)_二叉树_02 = 【树上倍增】最近公共祖先(LCA)_LCA_08【树上倍增】最近公共祖先(LCA)_树上倍增 的 第 【树上倍增】最近公共祖先(LCA)_父节点_04 个祖先节点是 【树上倍增】最近公共祖先(LCA)_树上倍增 的第 【树上倍增】最近公共祖先(LCA)_二叉树_12 个祖先节点的第 【树上倍增】最近公共祖先(LCA)_二叉树_12 个祖先节点(【树上倍增】最近公共祖先(LCA)_父节点_04 = 【树上倍增】最近公共祖先(LCA)_二叉树_15


【树上倍增】最近公共祖先(LCA)_LCA_16 数组用来记录 【树上倍增】最近公共祖先(LCA)_树上倍增

【树上倍增】最近公共祖先(LCA)_二叉树_18 数组需要用 【树上倍增】最近公共祖先(LCA)_二叉树_19

void dfs(int u, int pre){

dep[u] = dep[pre] + 1;
fa[u][0] = pre;

for (int i = 1; i <= (lg[dep[u]]); i++) {
// u 的2^i个祖先是u的2^(i-1)个祖先的2^(i-1)个祖先
// 2^i = 2^(i-1) + 2^(i-1)
fa[u][i] = fa[fa[u][i-1]][i-1];
}

for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
if(j == pre) continue;
dfs(j, u);
}
}

​预处理​​​ 【树上倍增】最近公共祖先(LCA)_算法_20

for (int i = 0; i < N; ++i) {
// lg 8 = lg7 + (1 << 3 == 8)
lg[i] = lg[i-1] + (1 << (lg[i-1] + 1) == i);
}

求解 【树上倍增】最近公共祖先(LCA)_LCA_21【树上倍增】最近公共祖先(LCA)_树上倍增_22【树上倍增】最近公共祖先(LCA)_二叉树_23 时,需要先 树上倍增 让 【树上倍增】最近公共祖先(LCA)_LCA_21【树上倍增】最近公共祖先(LCA)_树上倍增_22 处于同一层,然后如果 【树上倍增】最近公共祖先(LCA)_LCA_21【树上倍增】最近公共祖先(LCA)_树上倍增_22 是祖先关系的话,就返回此时的 【树上倍增】最近公共祖先(LCA)_LCA_21【树上倍增】最近公共祖先(LCA)_树上倍增_22 中任意的一个;
否则,【树上倍增】最近公共祖先(LCA)_LCA_21【树上倍增】最近公共祖先(LCA)_树上倍增_22 同时向上倍增,找到 【树上倍增】最近公共祖先(LCA)_二叉树_23 下面一层的节点,此时只需返回 【树上倍增】最近公共祖先(LCA)_算法_33【树上倍增】最近公共祖先(LCA)_LCA_34 (即【树上倍增】最近公共祖先(LCA)_LCA_21【树上倍增】最近公共祖先(LCA)_树上倍增_22任意一个的直接父节点)即可!


求证下面代码为何一定能找出 【树上倍增】最近公共祖先(LCA)_LCA_21【树上倍增】最近公共祖先(LCA)_树上倍增_22【树上倍增】最近公共祖先(LCA)_二叉树_23 的直接子节点 (即 【树上倍增】最近公共祖先(LCA)_二叉树_23下面两个节点)

首先此时【树上倍增】最近公共祖先(LCA)_LCA_21【树上倍增】最近公共祖先(LCA)_树上倍增_22 已经处于同一层;
【树上倍增】最近公共祖先(LCA)_LCA_43 = 【树上倍增】最近公共祖先(LCA)_父节点_44;
可以确定 【树上倍增】最近公共祖先(LCA)_LCA_21【树上倍增】最近公共祖先(LCA)_树上倍增_22【树上倍增】最近公共祖先(LCA)_二叉树_23 的深度(设为【树上倍增】最近公共祖先(LCA)_树上倍增_48) 一定在 【树上倍增】最近公共祖先(LCA)_LCA_49 ~ 【树上倍增】最近公共祖先(LCA)_二叉树_50 之间,
【树上倍增】最近公共祖先(LCA)_二叉树_23【树上倍增】最近公共祖先(LCA)_LCA_21【树上倍增】最近公共祖先(LCA)_树上倍增_22 的第 【树上倍增】最近公共祖先(LCA)_二叉树_54

【树上倍增】最近公共祖先(LCA)_算法_55 = 【树上倍增】最近公共祖先(LCA)_LCA_56【树上倍增】最近公共祖先(LCA)_父节点_57,显然 【树上倍增】最近公共祖先(LCA)_LCA_43 <= 【树上倍增】最近公共祖先(LCA)_二叉树_59
【树上倍增】最近公共祖先(LCA)_LCA_49 ~ 【树上倍增】最近公共祖先(LCA)_LCA_43 任何一个数都可以由 【树上倍增】最近公共祖先(LCA)_父节点_62,【树上倍增】最近公共祖先(LCA)_LCA_63,【树上倍增】最近公共祖先(LCA)_算法_64,…,【树上倍增】最近公共祖先(LCA)_二叉树_59 其中的 【树上倍增】最近公共祖先(LCA)_树上倍增_66 个相加求得 !!!(相当于 【树上倍增】最近公共祖先(LCA)_算法_55位二进制凑得 【树上倍增】最近公共祖先(LCA)_树上倍增_48

然而需要凑得 【树上倍增】最近公共祖先(LCA)_树上倍增_48 - 1 层(即 【树上倍增】最近公共祖先(LCA)_LCA_21 的第 【树上倍增】最近公共祖先(LCA)_父节点_71 个 父节点) 的话,需要将 【树上倍增】最近公共祖先(LCA)_算法_72 从大到小;如果 【树上倍增】最近公共祖先(LCA)_父节点_73,说明此时跳的话,会跳过头
【树上倍增】最近公共祖先(LCA)_父节点_71 一定小于当前的 【树上倍增】最近公共祖先(LCA)_父节点_04
所以 【树上倍增】最近公共祖先(LCA)_父节点_71 一定能由 【树上倍增】最近公共祖先(LCA)_父节点_77 * 【树上倍增】最近公共祖先(LCA)_二叉树_12 + 【树上倍增】最近公共祖先(LCA)_父节点_77 * 【树上倍增】最近公共祖先(LCA)_LCA_80 + … + 【树上倍增】最近公共祖先(LCA)_父节点_77 * 【树上倍增】最近公共祖先(LCA)_二叉树_82 + 【树上倍增】最近公共祖先(LCA)_父节点_77 * 【树上倍增】最近公共祖先(LCA)_父节点_62

for (int i = lg[dep[a]]; i >= 0; i--) {

if(fa[a][i] != fa[b][i]){
a = fa[a][i], b = fa[b][i];
}
}

​AC Code​

#include <iostream>
#include <cstring>

using namespace std;

const int N = 5e5 + 10;

int e[N << 1], ne[N << 1], h[N], cnt;

void add(int a, int b){
e[cnt] = b, ne[cnt] = h[a], h[a] = cnt++;
}

int n, m, s;
// 用于预处理log(i) 的值
int lg[N];
int dep[N]; // 记录某节点的深度
int fa[N][20]; // 记录某节点的祖先节点; fa[u][0]是父节点

// dfs预处理出来fa数组
void dfs(int u, int pre){

dep[u] = dep[pre] + 1;
fa[u][0] = pre;

for (int i = 1; i <= (lg[dep[u]]); i++) {
// u的2^i个祖先是u的2^(i-1)个祖先的2^(i-1)个祖先
// 2^i = 2^(i-1) + 2^(i-1)
fa[u][i] = fa[fa[u][i-1]][i-1];
}

for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
if(j == pre) continue;
dfs(j, u);
}
}

int lca(int a, int b){
// 设a的深度较深
if(dep[a] < dep[b]) swap(a, b);

// 让a,b到同一深度
for (int i = lg[dep[a]]; i >= 0; i--) {
// 利用倍增让a向上走
// 如果a的2^i个父节点的深度大于等于b,则a向上走
if(dep[fa[a][i]] >= dep[b]) a = fa[a][i];
}
if(a == b) return a;

// 找出a和b的lca的直接子节点(即lca下面两个节点)
for (int i = lg[dep[a]]; i >= 0; i--) {

if(fa[a][i] != fa[b][i]){
a = fa[a][i], b = fa[b][i];
}
}

return fa[a][0]; // fa[b][0]
}

int main(){

memset(h, -1, sizeof h);

lg[1] = 0;

for (int i = 0; i < N; ++i) {
// lg 8 = lg7 + (1 << 3 == 8)
lg[i] = lg[i-1] + (1 << (lg[i-1] + 1) == i);
}

scanf("%d%d%d", &n, &m, &s);

int x, y;
for (int i = 0; i < n - 1; ++i) {
scanf("%d%d", &x, &y);
add(x, y);
add(y, x);
}

dep[s] = 0;
dfs(s, s);

int a, b;
for (int i = 0; i < m; ++i) {
scanf("%d%d", &a, &b);
// printf("%d %d\n", dep[a], dep[b]);
printf("%d\n", lca(a, b));
}

return 0;
}

​树上倍增应用题​

#include <iostream>
#include <cstring>

using namespace std;

int n, cnt, e[1000], ne[1000], h[1000];
int lg[1000], dep[1000], wd[1000], fa[1000][30];

void add(int a, int b){

e[cnt] = b, ne[cnt] = h[a], h[a] = cnt++;
}

void dfs(int u, int pre){

dep[u] = dep[pre] + 1;

fa[u][0] = pre;

for (int i = 1; i <= lg[dep[u]]; ++i) {
fa[u][i] = fa[fa[u][i-1]][i-1];
}

for (int i = h[u]; i != -1; i = ne[i]) {

int j = e[i];
if(j == pre) continue;
dfs(j, u);
}

wd[dep[u]]++;
}


void lca(int a, int b){

int x = a, y = b;

// a 为深度深的
if(dep[a] < dep[b]) swap(a, b);

// a 上升到和 b 一样高
for (int i = lg[a]; i >= 0; i--) {

if(dep[fa[a][i]] >= dep[b]) a = fa[a][i];
}

for (int i = lg[a]; i >= 0; i--) {
if(fa[a][i] != fa[b][i]){
a = fa[a][i], b = fa[b][i];
}
}

// lca
a = fa[a][0];

cout << (dep[x] - dep[a]) * 2 + (dep[y] - dep[a]) << endl;
}

int main(){

memset(h, -1, sizeof h);

lg[1] = 0;
for (int i = 2; i < 1000; ++i) {
lg[i] = lg[i - 1] + (1 << (lg[i-1] + 1) == i);
}

cin >> n;

int x, y;

for (int i = 0; i < n - 1; ++i) {

cin >> x >> y;
add(x, y);
add(y, x);
}

dfs(1, 1);

int d = 0, w = 0;

for (int i = 0; i < 1000; ++i) {
d = max(d, dep[i]);
w = max(w, wd[i]);
}

cout << d << endl;
cout << w << endl;

int u, v;

cin >> u >> v;

lca(u, v);

return 0;
}