下图是这篇文章的思路导航,本人将按照这个模式学习该算法。此文为我的学习笔记,如有差错感谢大家斧正。
一,最初的目的
显然就是求LCA了,LCA是啥? 中文解释为树上两点的最近公共祖先。有一个小bug,当两个点在同一条支上的时候,LCA是可以为其中一个点的。也就是说,LCA是可以包括着两个点本身的。并不是严格意义上的祖先。
这里就加入一道板子题了:(题目链接P3379 【模板】最近公共祖先(LCA))
题目答意:给你一棵树,有 m 次查询,每次问你 a, b,树上两点的最短距离。
万物始于暴力,LCA的朴素算法显然也很简单,就是先让深度较深的向上爬直到两点在同一深度上,如果这时两点相遇(一点在以另一点为根的子树上)则LCA为当前位置。如果还没有相遇,就让两点同时向上移动,直到两点相遇(两点都在以LCA为根的子树上)。这里的移动显然是最耗费时间,如果有n个点m次查询就为O()当树退化时接近O(
)
二,算法结构
首先这是一个树,不知道的可以出门找度娘。
倍增显然就是需要加速朴素算法中的上升过程了,这里运用了一个类似DP的东西。
我们要初始化一个 数组,这个数组表示,
这个节点由下往上第
位祖先是谁。显然就有初状态
为
的父节点,还有状态转移方程
。这个转移方程的中文表示法就是:我的第
位祖先的第
位祖先是我的第
位祖先。
然后的过程就和朴素算法一样了,爬高高。
第一情况种:
a 上升到与 b 同高相遇,LCA此时为 b 。
第二种情况:
a b 同高后依然不相遇 , 就让 a b 同时向上 直到 a b 同父。
然后是代码部分:
struct node_e{
int v,en;
}e[maxn<<1];
int he[maxn],cnt;//前项星三件套
int vis[maxn],dep[maxn];//标记,深度
int anc[maxn][25];//祖先
int n,m,s;
void ade(int a,int b){
e[++cnt].v=b;e[cnt].en=he[a];he[a]=cnt;
}
void dfs(int s){//初始化
vis[s] = 1;
for(int i=1;i<21;++i){
if(dep[s]<(1<<i)) break;
anc[s][i]=anc[anc[s][i-1]][i-1];//倍增求祖先
}
for(int i=he[s];i;i=e[i].en){
int v = e[i].v;
if(vis[v]) continue;
dep[v]=dep[s]+1;
anc[v][0]=s;
dfs(v);
}
}
int Lca(int a,int b){
if(dep[a]<dep[b]){int tt=a;a=b;b=tt;}//确保a在下面
int t = dep[a]-dep[b];
for(int i=0;i<21;i++){
if(t&(1<<i)) a=anc[a][i];//a向上爬到和b同样的高度
}
if(a==b) return a;//a,b在一棵子树上
for(int i=20;i>=0;i--){//不在
if(anc[a][i]!=anc[b][i]){
a=anc[a][i];
b=anc[b][i];
}
}
return anc[a][0];
}
三,理解深入(未完待续)
四,刷题