下圖是這篇文章的思路導航,本人將按照這個模式學習該算法。此文爲我的學習筆記,如有差錯感謝大家斧正。
一,最初的目的
顯然就是求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];
}
三,理解深入(未完待續)
四,刷題