题面原文

原题链接

题目描述

如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

输入格式

第一行包含三个正整数 N,M,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来 N-1 行每行包含两个正整数 x, y,表示 x 结点和 y 结点之间有一条直接连接的边(数据保证可以构成树)。
接下来 M行每行包含两个正整数 a, b表示询问 a 结点和 b 结点的最近公共祖先。

输出格式

输出包含 M 行,每行包含一个正整数,依次为每一个询问的结果。

Ac Record

Code

show me the code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <iostream>
#include <cmath>
#include <vector>
#include <cstdio>
using namespace std;
const int maxn=500001;
vector<int> node[maxn];
int dep[maxn];
int f[maxn][22]; //2^20
int lowbit(int x){
return log2(x&(-x));
}
void dfs(int u,int fa,int d){
dep[u]=d;
f[u][0]=fa;
for(int i=0;i<node[u].size();++i){
if(node[u][i]==fa) continue;
dfs(node[u][i],u,d+1);
}
}
int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
while(dep[x]>dep[y]){
x=f[x][lowbit(dep[x]-dep[y])];
}
if(x==y) return x;// or return y.
for(int i=log2(dep[x]);i>=0;i--){
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
inline int read()
{
char c=(char)getchar();
int x=0,fs=1;
while(!isdigit(c)){if(c=='-')fs=-1;c=(char)getchar();}
while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=(char)getchar(); //NOLINT
return x*fs;
}

void write(int a)
{
if(a<0) putchar('-'),a=-a;
if(a>=10)write(a/10);
putchar(a%10+48);
}
int main() {
int n,m,s;
cin>>n>>m>>s;
for(int i=1;i<n;++i){
int x,y;
x=read();
y=read();
node[x].push_back(y);
node[y].push_back(x);
}
dfs(s,s,0);
for(int i=1;i<22;++i){
for(int u=1;u<=n;u++){
f[u][i]=f[f[u][i-1]][i-1];
}
}
for(int i=1;i<=m;++i){
int x,y;
x=read();
y=read();
cout<<lca(x,y)<<endl;
}
return 0;
}

思路

这就是一道LCA模板题。

  • 简单地用vector存图。
  • 先用DFS把整个树遍历一遍,标记上每个节点的深度。这个深度的作用会在后面的算法体现到。
  • f[u][i]=f[f[u][i-1]][i-1];这是倍增的递推式。
    • f[u][i]指的是,节点u的前2i2^i个父节点。
    • 例如f[u][0]指的是就是节点u的父节点。f[u][1]指的是就是节点u的父节点的父节点。f[u][2]指的是就是节点u的父节点的父节点的父节点的父节点。
    • 在这个题目中的节点数最多是500000,所以最坏情况下是2192^{19},所以数组开到20就差不多够用了。(为保险可以多开一点)
  • 用LCA函数求解答案。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
while(dep[x]>dep[y]){
x=f[x][lowbit(dep[x]-dep[y])];
}
if(x==y) return x;// or return y.
for(int i=log2(dep[x]);i>=0;i--){
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
  • 先将两个节点移动至同一深度。
    • 这个操作可以通过不断使更深的节点变为它的父节点就行了。
    • 为了快速移动,通过倍增来实现。
  • 如果移动至同一深度,两个节点变成同一节点了,那么就可以返回了。
  • 如果不是同一节点,那么就再同时移动两个节点(变为它们的父节点),直至最后一个节点的下一层节点。
  • 这个时候返回这层节点的父节点即为答案。