P3379 【模板】最近公共祖先(LCA)
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入格式
第一行包含三个正整数 N,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来 N−1 行每行包含两个正整数 x, y,表示 x 结点和 y 结点之间有一条直接连接的边(数据保证可以构成树)。
接下来 M 行每行包含两个正整数 a,b,表示询问 a 结点和 b 结点的最近公共祖先。
输出格式
输出包含 M 行,每行包含一个正整数,依次为每一个询问的结果。
输入输出样例
输入
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
输出
4
4
1
4
4
说明/提示
对于 30% 的数据,10<=N≤10,10<=M≤10。
对于 70% 的数据,10000<=N≤10000,10000<=M≤10000。
对于 100% 的数据,500000<=N≤500000, 500000<=M≤500000。
在暴力法找LCA中,我们每次向上跳一格,显然速度太慢,所以我们用倍增的方法,来尽量减少向上跳得次数;
假设求x,y的LCA,其具体做法如下:
①
先求出倍增表:(因为二进制可以表示十进制下的任何数,所以一定可以跳到你想跳到的点。)
因为22 = 21 * 21,说明你要跳22步,可以先跳21步再跳21步,即2 * 21,故你要跳到u号结点的2j的祖先,可以先跳2j-1再从u的2j-1的祖先处跳2j-1,即2 * 2j-1。
for(int i=1;(1<<i)<=dep[u];i++) f[u][i] = f[f[u][i-1]][i-1];
②
先让结点x,y的深度相同,我们让深度深的结点尽可能的向上跳,直到dep[x] = dep[y],跳到深度相同时(可能一开始深度就相同),我们判断一下,此时x,y是否重合(树可能是一条链),如果重合直接返回;
if(dep[x] < dep[y]) swap(x,y);
for(int i=20;i>=0;i--) {
if(dep[f[x][i]] >= dep[y]) x = f[x][i];
if(x==y) return x;
}
③
我们让x,y结点尽可能同时往上跳,但是跳到的结点不能重合,因为那样会导致跳到的结点是公共祖先,但可能不是最近的。最终两节点会跳到其最近公共祖先的儿子节点,此时返回x或y的父亲结点即可
for(int i=20;i>=0;i--) if(f[x][i] != f[y][i]) x = f[x][i],y = f[y][i];
return f[x][0];
Code:
#include<bits/stdc++.h>
#define bug(x) cout<<#x<<" = "<<x<<endl;
using namespace std;
typedef long long ll;
const int N = 5e5 + 10;
const int inf = 0x3f3f3f3f;
int cnt;
int n,m,s;
int dep[N],head[N],f[N][30];
struct Node {
int to,next;
}e[N<<2];
void add_edge(int u,int v) {
cnt++;
e[cnt].to = v;
e[cnt].next = head[u];
head[u] = cnt;
}
void dfs(int u,int fa) {
dep[u] = dep[fa] + 1;
for(int i=1;(1<<i)<=dep[u];i++) f[u][i] = f[f[u][i-1]][i-1];
for(int i=head[u];i;i=e[i].next) {
int to = e[i].to;
if(to == fa) continue;
f[to][0] = u;
dfs(to,u);
}
}
int LCA(int x,int y) {
if(dep[x] < dep[y]) swap(x,y);
for(int i=20;i>=0;i--) {
if(dep[f[x][i]] >= dep[y]) x = f[x][i];
if(x==y) return x;
}
for(int i=20;i>=0;i--) if(f[x][i] != f[y][i]) x = f[x][i],y = f[y][i];
return f[x][0];
}
int main() {
cin>>n>>m>>s;
for(int i=1;i<n;i++) {
int u,v;
scanf("%d%d",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
dfs(s,0);
while(m--) {
int x,y;
scanf("%d%d",&x,&y);
printf("%d\n",LCA(x,y));
}
return 0;
}
/*
*/