1. 程式人生 > >洛谷3379 【模板】最近公共祖先(LCA) 樹上倍增+LCA

洛谷3379 【模板】最近公共祖先(LCA) 樹上倍增+LCA

題目

如題,給定一棵有根多叉樹,請求出指定兩個點直接最近的公共祖先。

題解

樹上倍增和普通的倍增原理是一樣的,它的運用很廣泛,除了求LCA外,在很多問題中都有應用
倍增就是將狀態空間中2的整數次冪的值作為代表,當要查詢其它位置的值時,可以通過“任意整數可以表示成若干個2的次冪項的和”這一性質,使用之前求出的代表值拼成所需的值。

在樹上倍增求LCA中,設f[i][k]表示點i的2^k輩父親,而f[i][0]表示i的父節點。通過一個廣搜或深搜預處理出所有節點的f,還有節點的深度。其中f[i][k]=f[f[i][k-1]][k-1],1<=k<=log(n)/log(2)+1
求LCA時,先把兩個節點弄到同一高度,然後一起往上跳,最終跳到的一定是它們最近公共祖先的子節點,答案就得出了(詳見註釋)

程式碼

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <queue>

using namespace std;

const int N=500005;
int n,m,s,t,cnt;
int ls[N],ne[N*2],to[N*2],b[N];
int f[N][22];
queue<int> q;

void bfs(){
    q.push(s);b[s]=1;f[s][0]=s;
    while (q.size()){
        int
k=q.front(),e; q.pop(); e=k; for (k=ls[k];k;k=ne[k]){ if (b[to[k]]) continue; b[to[k]]=b[e]+1; f[to[k]][0]=e; for (int i=1;i<=t;i++) f[to[k]][i]=f[f[to[k]][i-1]][i-1]; q.push(to[k]); } } } int
lca(int c,int d){ if (b[c]>b[d]) swap(c,d);//比較的是深度,不是點的標號!!!! for (int i=t;i>=0;i--) if (b[f[d][i]]>=b[c]) d=f[d][i];//把深度大的跳到和深度小的同一深度 //只在d的父親的深度大於等於深度小的c時跳,這樣就不會跳過頭,通過2的次冪組合可以剛好跳到 if (c==d) return c;//當c剛好就是LCA時 for (int i=t;i>=0;i--) if (f[c][i]!=f[d][i]) c=f[c][i],d=f[d][i]; //一起跳,只在兩點父輩不同時跳,可以通過2的次冪項組合跳到LCA的兒子節點 return f[c][0]; } int main(){ scanf("%d%d%d",&n,&m,&s); t=(int)(log(n)/log(2))+1; for (int i=1;i<n;i++){ int a,b; scanf("%d%d",&a,&b); ne[++cnt]=ls[a];ls[a]=cnt;to[cnt]=b; ne[++cnt]=ls[b];ls[b]=cnt;to[cnt]=a; } bfs(); for (int i=1;i<=m;i++){ int a,b; scanf("%d%d",&a,&b); printf("%d\n",lca(a,b)); } }