首先介绍一下朴素的O(N)算法
比如我们在树上要找x,y两个节点的LCA,首先我们将两个节点移动到同一深度位置(将深度深的结点向上移动,直到两个节点的深度相同),而后两个节点共同向上移动,直到移动到同一个节点,这个节点就是LCA
具体看代码:
int find_root(int u,int v){
int depth_u=buf[u].depth;
int depth_v=buf[v].depth;
if(depth_u
对于小数据,这个算法已经够用,但如果数据更大,那么就需要寻找更快的算法。
比如P3379 【模板】最近公共祖先(LCA)
n取到了500000,由于询问占用0(N)的时间,这就要求我们求LCA的复杂度应优化到log(N)。由此,我们引入倍增法LCA。
那么,我们观察朴素算法,可以发现时间主要花在两个地方:
在朴素算法里,我们是一步一步向上移动的,那么,有没有办法把步子跨得大一下呢?
这里我们用二进制的方法想一想,举个例子,如果我们想向上移动11步,11的二进制是(1011B),那么我们是不是可以第一次先向上移动8(1000B)步,第二次移动2(10B)步,第三次移动1(1B)步呢。这样,我们就把原来需要移动11步优化到了3步。
有了上述思想,我们就可以在预处理阶段处理一个数组jump,令jump[x][i]表示节点x向上跳2^i步到达的位置
还以上述11步为例,假如树的最大深度是1000,那么2^10=1024就可以表示所有深度的结点
我们跳11步,相当于跳00000001011步,这样我们从跳2^10开始尝试:
for(int i=10;i>=0;i--){
if(depth[jump[a][i]]>=depth[b]){
a=jump[a][i];
}
}
由二进制可以看出,显然i=10~4时都不跳(如果跳,则跳的总步数一定大于11),只在i=3,1,0时跳
这样,深度1000的树,最坏情况跳1000步,我们用10次循环就可以完美解决。
有了第一个问题解决的思想,我们同样可以用这个思路解决第二个问题。不同的是,我们这次的目标是优化跳到LCA下面一层的结点,即只有两个节点跳到不是同一个位置时才跳。这一点读者可以结合二进制和代码自己理解一下。
int lca(int a,int b){
if(depth[b]>depth[a])swap(a,b);
for(int i=20;i>=0;i--){//移动到同一深度位置
if(depth[jump[a][i]]>=depth[b]){
a=jump[a][i];
}
}
if(a==b)return a;
for(int i=20;i>=0;i--){//共同向上移动查找LCA
if(jump[a][i]!=jump[b][i]){
a=jump[a][i];
b=jump[b][i];
}
}
return jump[a][0];//跳到的是LCA的下方结点
}
那么,现在的问题就到了怎么预处理jump这个数组上了,这里就用到了倍增的思想。
倍增思想的核心公式是2^i=2^(i-1)+2^(i-1) eg.2^10=2^9+2^9 (1024=512+512)
那么,X向上跳2^i步,是不是可以理解为X向上跳2^(i-1)步后再跳2^(i-1)步呢。是不是也可以理解为X向上跳2^(i-1)步的这个节点再跳2^(i-1)步呢。
由此我们就得到了更新公式 jump[x][i+1]=jump[jump[x][i]][i]; 这样更新为什么对呢,首先我们知道当更新 jump[x][i+1]时jump[x][i]已经计算过,并且我们是从根节点向下逐层节点初始化,因此jump[x][i]这个节点的jump[jump[x][i]]都已被更新过,所以每一步中jump[jump[x][i]][i]都是已经计算过的值。这样,我们就可以逐层更新,得到所有结点的jump
void preprocessing(int x,int fa){
depth[x]=depth[fa]+1;
jump[x][0]=fa;//向上跳一步是x的父亲节点
for(int i=0;i<20;i++){//更新x结点的jump数组
jump[x][i+1]=jump[jump[x][i]][i];
}
vector::iterator it;
for(it=buf[x].begin();it!=buf[x].end();it++){//扩展更新x的子节点
if(*it==fa)continue;
preprocessing(*it,x);
}
}
以下是本题的AC代码
这道题对数据卡的非常严格,除了算法本身,输入输出时间,存数的数据结构也同样需要注意效率。
本代码使用cin输入会TLE
#include
#include
#include
#include
#include
using namespace std;
int n,m,s;
vector buf[500005];
int depth[500005];
int jump[500005][25];
void preprocessing(int x,int fa){
depth[x]=depth[fa]+1;
jump[x][0]=fa;
for(int i=0;i<20;i++){
jump[x][i+1]=jump[jump[x][i]][i];
}
vector::iterator it;
for(it=buf[x].begin();it!=buf[x].end();it++){
if(*it==fa)continue;
preprocessing(*it,x);
}
}
int lca(int a,int b){
if(depth[b]>depth[a])swap(a,b);
for(int i=20;i>=0;i--){
if(depth[jump[a][i]]>=depth[b]){
a=jump[a][i];
}
}
if(a==b)return a;
for(int i=20;i>=0;i--){
if(jump[a][i]!=jump[b][i]){
a=jump[a][i];
b=jump[b][i];
}
}
return jump[a][0];
}
int main(){
cin>>n>>m>>s;
int x,y;
for(int i=0;i