树上倍增求LCA详解
LCA(least common ancestors)最近公共祖先
指的就是對于一棵有根樹,若結(jié)點(diǎn)z既是x的祖先,也是y的祖先(不要告訴我你不知道什么是祖先),那么z就是結(jié)點(diǎn)x和y的最近公共祖先。
定義到此。
那么怎么求LCA?
對于樸素思想,就是我要一步一步往上爬。。一步一步走。先把結(jié)點(diǎn)x和y整到同一深度,然后再一次一個深度的往上查,直到祖先一樣才break(明顯是個while)
但是,一步一步實(shí)在是太慢了,所以不能腳踏實(shí)地地走
那么,考慮跳著走,
跳著走的條件就是要滿足一步步數(shù)盡可能多并且不跳過了。當(dāng)然你跳過了在一步一步往下找也行啊QWQ
于是,在滿足這兩個條件的情況下,我們有了LCA算法:
一步條2的n次方。
對于每一步跳躍,我們要預(yù)處理一個二維數(shù)組f(father)f[x][i],表示對于當(dāng)前的x結(jié)點(diǎn),往上跳2^i個祖先,特別的,像數(shù)學(xué)中的,f[x][0]就是x的一代祖先。于是我們要用一個dfs預(yù)處理來解決這個f數(shù)組。后面我們會提到;
處理完f數(shù)組,那就很簡單了。
輸入x和y,表示要求結(jié)點(diǎn)x和結(jié)點(diǎn)y的LCA,那么我們開始求LCA:
對于x和y在不同高度,我們先把他們拉到一個高度,同樣不能一步一步走,也要用到f數(shù)組。
這里我們要提前了解到一個定理:對于任意一個非零整數(shù),我們都可以將他用2的次冪表示出來。也就是such as? : 11=2^3+2^1+2^0。這倒也不用證明,就像每個數(shù)都可以用二進(jìn)制表示一樣。
接著講:在把x和y弄到同一高度時,我們要先做的是設(shè)x的深度dep要比y的深度dep要大,如果dep[y]>dep[x],那么把他們交換。原因是如果不交換,那么我們需要判斷兩種情況,用兩個if以及幾乎相同的代碼。。(亂七八糟還占內(nèi)存)
然后用一個判斷??? if(dep[f[x][i]]>=dep[y])x=f[x][i];(此時x深度大于y),在這里用外層for循環(huán)來枚舉i,但是i一定要從大到小!。為什么?
安利一個故事(其實(shí)也不算):一個玻璃瓶,裝了幾塊石頭,滿到瓶口。滿了嗎?沒有。又裝了一些沙子,滿到瓶口。滿了嗎?沒有。最后又裝滿了水,滿到瓶口。終于滿了。
那么,類比于x和y的深度的距離,這個瓶子的容積也是同樣道理。從2的盡可能大的次冪去找,一旦能找到能接近他們的i,就更新dep[x],直到相等。類似于無限逼近,最后值相等的過程。如果i相等了,就說明他們的深度已經(jīng)在同一層了。
與倍增到同一深度的過程相比,倍增找公共祖先也是類似的,但是有一點(diǎn)不同,就是你不知道什么時候找到他們的公共祖先,因此就沒有查找的上限,那么就讓他們盡可能接近。因此條件改成了這個:
if(f[x][i]!=f[y][i])
?? ??? ?{
?? ??? ??? ?x=f[x][i];y=f[y][i];
?? ??? ?}
在執(zhí)行完這個語句后,我們成功讓x和y變成了某一節(jié)點(diǎn)z的左右兒子!
因?yàn)樽笥覂鹤邮亲罱咏怯植幌嗟鹊摹?/span>
那么我們隨便取其中一個找爸爸,就找到了LCA。
啊。。。。。。喘口氣
AC代碼:
有關(guān)鏈?zhǔn)酱鎴D不懂的的點(diǎn)這里。
?
#include<cstdio> #include<iostream> using namespace std; int n,m,s,num=0,head[1000001],dep[1000001],f[1000001][23]; int a1,a2; struct edg{int next,to; }edge[1000001]; void edge_add(int u,int v)//鏈?zhǔn)角跋蛐谴鎴D {num++;edge[num].next=head[u];edge[num].to=v;head[u]=num;edge[++num].next=head[v];edge[num].to=u;head[v]=num; } void dfs(int u,int father)//對應(yīng)深搜預(yù)處理f數(shù)組 {dep[u]=dep[father]+1;for(int i=1;(1<<i)<=dep[u];i++){f[u][i]=f[f[u][i-1]][i-1];}for(int i=head[u];i;i=edge[i].next){int v=edge[i].to;if(v==father)continue;//雙向圖需要判斷是不是父親節(jié)點(diǎn) f[v][0]=u;dfs(v,u);} } int lca(int x,int y) {if(dep[x]<dep[y])swap(x,y);for(int i=20;i>=0;i--)//從大到小枚舉使x和y到了同一層 {if(dep[f[x][i]]>=dep[y])x=f[x][i];if(x==y)return x;}for(int i=20;i>=0;i--)//從大到小枚舉 {if(f[x][i]!=f[y][i])//盡可能接近 {x=f[x][i];y=f[y][i];} } return f[x][0];//隨便找一個**輸出 } int main(){scanf("%d%d%d",&n,&m,&s);for(int i=1;i<n;i++){scanf("%d",&a1);scanf("%d",&a2);edge_add(a1,a2);//鏈?zhǔn)酱孢? }dfs(s,0);for(int i=1;i<=m;i++){scanf("%d %d",&a1,&a2);printf("%d\n",lca(a1,a2));//求兩個節(jié)點(diǎn)的LCA } }?
完結(jié)。。
?
轉(zhuǎn)載于:https://www.cnblogs.com/lbssxz/p/11114819.html
總結(jié)
以上是生活随笔為你收集整理的树上倍增求LCA详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Vue3、TypeScript 实现图片
- 下一篇: 移动端抓包合集