LCA
LCA (Lowest Common Ancestors) 最近公共祖先。对于有根树 T T T的两个结点 u u u、 v v v,最近公共祖先 L C A ( T , u , v ) LCA(T,u,v) LCA(T,u,v) 表示一个结点 x x x,满足 x x x是 u u u和 v v v的祖先且 x x x的深度尽可能大。在这里,一个节点也可以是它自己的祖先。
解决LCA问题我们有两种做法:暴力和倍增。
暴力
无需多言
- 使用链式前向星,将整棵树看为无向无环图,双向加边。
- dfs标记父亲节点,标记深度。
- 将需要查询的两个点调整为同一深度,若两个点仍不相同,访问他们的父亲节点直到相同为止。
#include<iostream>
#include<cstdio>
#define maxn 500005
using namespace std;
int n,m,root;
int dep[maxn]; //节点深度
int fa[maxn]; //父亲节点
int head[maxn]; //邻接表head[]
struct node{
int to,next;
}e[maxn<<1];
//加边
void add(int u,int v){
e[++cnt].to =v;
e[cnt].next =head[u];
head[u]=cnt;
}
//找爹 标记fa[] dep[]
void dfs(int p,int f){
fa[p]=f; //标记父亲节点 (认爹
dep[p]=dep[f]+1; //标记深度 (认清地位
for(int i=head[p];i;i=e[i].next)//不停找爹
if(e[i].to !=f) dfs(e[i].to ,p);
}//遍历完之后 循环自动跳出 认爹环节结束
int lca(int u,int v){
if(u==v) return u; //找到公共点 跳出
else if(dep[u]==dep[v]) return lca(fa[u],fa[v]); //深度相同 访问父亲节点
else if(dep[u]<dep[v]) return lca(u,fa[v]); //深度不同 提升较深节点
else return lca(fa[u],v); //同上
}
int main(){
scanf("%d%d%d",&n,&m,&root);
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v); add(v,u); //两次加边
}
dfs(root,root); //从根节点开始认爹
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
printf("%d\n",lca(u,v));
}
return 0;
}
倍增
- 双向加边 预处理 l g [ i ] lg[i] lg[i] ( l g [ i ] = l o g 2 i + 1 lg[i]=log_2i+1 lg[i]=log2i+1)
- dfs标记 f a [ ] fa[] fa[], d e p [ ] dep[] dep[]
- 让两点跳到同一深度,再从根节点向下找lca
#include<iostream>
#include<cstdio>
#define maxn 500005
using namespace std;
int n,m,root,cnt;
int head[maxn];
int fa[maxn][40],lg[maxn],dep[maxn];
struct node{
int to,next;
}e[maxn<<1];
void add(int u,int v){
e[++cnt].to =v;
e[cnt].next =head[u];
head[u]=cnt;
}
void dfs(int p,int f){
fa[p][0]=f; //标记父亲
dep[p]=dep[f]+1; //标记深度
for(int i=1;(1<<i)<=dep[p];i++) //标记倍增祖先
fa[p][i]=fa[fa[p][i-1]][i-1]; //p的2^i祖先 = p的2^(i-1)祖先的 2^(i-1)祖先
for(int i=head[p];i;i=e[i].next) //一直找爹
if(e[i].to!=f) dfs(e[i].to,p);
}
int lca(int u,int v){
if(dep[u]>dep[v]) swap(u,v); //不妨设 u的深度 <=v的深度
for(int i=20;i>=0;i--) //跳到同一深度
if(dep[v]-(1<<i)>=dep[u])
v=fa[v][i];
if(u==v) return u; //找到公共祖先
for(int i=20;i>=0;i--) //为了防止误判 从根开始往下找lca
if(fa[u][i]!=fa[v][i]) //在找到lca前 他们的祖先都是相等的 要跳到LCA的下一层
u=fa[u][i], v=fa[v][i];
return fa[u][0]; //输出父亲节点
}
int main(){
scanf("%d%d%d",&n,&m,&root);
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v); add(v,u);
}
dfs(root,0);//找爹 标记数组
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
printf("%d\n",lca(u,v));
}
return 0;
}