最近公共祖先简称LCA(Lowest Common Ancestor)。两个节点的最近公共祖先,就是这两个点的公共祖先里面,离根最远的那个。为了方便,我们记某点集S=v1,v2,.......,vn的最近公共祖先为LCA(S)
1 LCA (u) = u
2 u 是v 的祖先,当且仅当LCA (u, v) = u
3 如果u 不为v 的祖先,并且v 不为u 的祖先,那么u, v 分
别处于LCA (u, v) 的两颗不同子树中
4 前序遍历中,LCA (S) 出现在所有S 中元素之前,后序遍历
中LCA (S) 则出现在所有S 中元素之后
5 两点集并的最近公共祖先为两点集分别的最近公共祖先的最
近公共祖先,即LCA(A ∪ B) = LCA(LCA(A), LCA(B))
6 两点的最近公共祖先必定处在树上两点间的最短路上
7 d(u, v) = h(u)+h(v)−2h(LCA(u, v)),其中d 是树上两点间
的距离,h 代表某点到树根的距离。
这里说两种方法 一种是朴素算法 一种是倍增算法
朴素算法很简单,我们首先要预处理出来所有节点所在的深度,对于需要查询的两点u,v我们假声深度较深的点为u,将u上移至与v相同深度的位置,然后u,v同时向上移动,直至两节点相遇,相遇的点即为LCA(u,v)
最坏复杂度O(N)
倍增算法求LCA就是利用倍增法对朴素算法的一个优化,预处理时间复杂度O(nlogn),可以将查询的时间复杂度降到O(logN)
总的时间复杂度:O(ologn+qlogn)
倍增算法讲解详细地址https://blog.youkuaiyun.com/JarjingX/article/details/8180560
这个有对倍增算法的详细讲解 我就不多说了 毕竟我刚学也是一个憨憨
对于LCA的题需要进行预处理 就是利用倍增算法
我们经过预处理可以得到这样一个表格
节点u | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
depth[u] | 1 | 2 | 3 | 3 | 4 | 4 | 5 | 6 |
fa[u][0] | 0 | 1 | 2 | 2 | 3 | 4 | 5 | 7 |
fa[u][1] | 0 | 0 | 1 | 1 | 2 | 2 | 3 | 5 |
fa[u][2] | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 2 |
这一题的大致思路就是
比如要求6 和 8的公共祖先 首先要让8这个节点向上移动到与6相同辈分的节点也就是5这个节点
然后要怎么移动呢 朴素的算法就是一步一步的移动 一次性的移动到5但是如果数据很多怎么办呢?
如何移动?这就需要用到倍增算法
我们首先要知道倍增算法的核心就是任何一个数都可以分解成二进制的数 比如7就可以分解成111
那么就可以先移动2的平方然后再移动2的一次方然后再移动2的零次方也就是这个意思这样只需要移动三次就够了
而要是正常的移动的话需要移动八次因此倍增算法就可以运用到这上边来。
话不多说,看具体的数据初始化吧!
首先要定义一个二维数组fa[i][j]表示节点i 的第2j 个祖先,我们可以得到递推式
fa[i][j]=fa[fa[i][j-1]][j-1]//这个就可以快速的进行倍增跳了
即:节点i的第2j 是i 的第2j−1 个节点的第2j−1 个节点
定义一个lg[i] 数组,代表当前节点u 所在的深度i 可以往
上跳的最大步数,使得ilg[i] ≤ depth[u]
根据一道模版题看代码吧!
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入格式
第一行包含三个正整数N、M、S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来N-1行每行包含两个正整数x、y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树)。
接下来M行每行包含两个正整数a、b,表示询问a结点和b结点的最近公共祖先。
输出格式
输出包含M行,每行包含一个正整数,依次为每一个询问的结果。
输入输出样例
输入 #1复制
5 5 4 3 1 2 4 5 1 1 4 2 4 3 2 3 5 1 2 4 5
输出 #1复制
4 4 1 4 4
说明/提示
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=10,M<=10
对于70%的数据:N<=10000,M<=10000
对于100%的数据:N<=500000,M<=500000
样例说明:
该树结构如下:
第一次询问:2、4的最近公共祖先,故为4。
第二次询问:3、2的最近公共祖先,故为4。
第三次询问:3、5的最近公共祖先,故为1。
第四次询问:1、2的最近公共祖先,故为4。
第五次询问:4、5的最近公共祖先,故为4。
故输出依次为4、4、1、4、4。
#pragma GCC optimize(2)//O2优化 可以加快写入的速度
#include<bits/stdc++.h>
using namespace std;
const int N = 1000000;
long long bit[N];//进行倍增的记录 也就是记录2的i次方
int depth[N],f[N][30];//构建数组 depth数组是记录深度也就是先记录两个节点的距离然后纪录如何到达公共祖先
vector<int> G[N];//存图 定义一个不定数组进行存图
void init(){
bit[0]=1;//记录2的零次方就是1
for(int i=1;i<=29;i++) bit[i]=(bit[i-1]*2);//一次记录2的i次方
}
void dfs(int u,int par){
depth[u]=depth[par]+1;//u节点是par节点的儿子 所以u节点 的深度比par节点的深度多一
f[u][0]=par;//分别记录 f[u][0]中的0就是u节点的父亲 1表示u节点的爷爷往上类推
for(int i=1;i<=29;i++) f[u][i]=f[f[u][i-1]][i-1]; //倍增方法类存图 比如说你要求2的i次方 可以先求2的i-1次方 然后加上2的i-1次方 也就是如上图要从8到1深度是5
//5换成2进制就是 101就是可以先向上移动四个单位 就是2然后再让2向上移动就到1了 而f[2][0]=1已经保存过可以直接用省时间
for(int i=0;i<(int)G[u].size();i++)//便利与u相关的所有点
{
int v=G[u][i];//然后从与u相关联的点开始便利
if(v==par) continue;//如果v与par这个点相关联就跳出循环
dfs(v,u); //继续进行标记存图
}
}
int lca(int a,int b){
if(depth[a]<depth[b]) swap(a,b);//如果 a的深度小于b 就让a和b交换一下
int dif=depth[a]-depth[b];//取a,b两者的深度差
for(int i=29;i>=0;i--)
{
if(dif>=bit[i])//逐渐便利 当俩着深度差为0的时候结束
{
a=f[a][i];
dif-=bit[i];
}
}
if(a==b) return a;//如果此时a和b相等就已经找到了共同祖先
for(int i=29;i>=0;i--)
{
if(depth[a]>=bit[i]&&f[a][i]!=f[b][i])//两个节点一同向上进行查找
{
a=f[a][i];b=f[b][i];
}
}
return f[a][0];
}
int main(){
init();
int n,m,s;
scanf("%d %d %d",&n,&m,&s);
int u,v;
for(int i=1;i<=n-1;i++){
scanf("%d %d",&u,&v);
G[u].push_back(v);//存图
G[v].push_back(u);//存图
}
dfs(s,0);
while(m--){
scanf("%d %d",&u,&v);
printf("%d\n",lca(u,v));
}
return 0;
}