1. 程式人生 > 實用技巧 >[筆記]倍增求LCA

[筆記]倍增求LCA

[筆記]倍增求LCA

原題鏈

演算法描述

LCA是指兩個點的最近公共祖先.

在程式中設f[x][k]表示x2k*輩祖先,即從x向上(根節點)走*2k步所到達的節點,如果向上走到的節點不存在則令f[x][k] = 0,並且f[x][0]為x節點的父節點,因為2^0 = 1,且任意一個節點向上走一步到達的就是它的父節點.同時我們會發現一個性質:∀k∈[1,log(n)],f[x][k] = f[f[x][k - 1][k - 1]],這個式子的意思是x向上跳2k*步所到達的節點與x向上跳*2(k-1)步所到達的節點再向上跳*2(k-1)*步所到達的節點是相同的,可以用**同底數冪相加**的原理證明~~(a

b + a^c = a^(b+c))~~.以上為預處理部分.


如何求LCA:設dis[x]為x節點的深度,並規定dis[x] ≥ dis[y]如果不滿足這個條件則交換x,y;此時我們保證了x的深度一定大於y,所以我們先嚐試將x向上跳2(logn)步,…跳21步,跳2^0步,並檢查所跳到的節點是否仍比y深,如果仍比y深則繼續上跳.如果調到x==y則說明x,y已經到達了兩者的LCA處,直接輸出.但如果當兩者的深度一致時x,y仍不相等,此時將x,y同時上跳直到f[x][i] == f[y][i],則說明此時x,y的父節點是同一個點,即為兩者的LCA.演算法結束

AC程式碼

#include <bits/stdc++.h>
using namespace std;
struct node{
	int to,next;
}edge[1000010];
int fir[1000010],n,m,s,tot,t,dis[500010],f[500010][35];
void add(int x,int y){//前向星存邊
	tot++;
	edge[tot].to = y;
	edge[tot].next = fir[x];
	fir[x] = tot;
}
void bfs(){//預處理出每個點的深度,並求出f陣列
	memset(dis,0,sizeof(dis));
	queue < int > q;
	while(!q.empty())q.pop();
	dis[s] = 1;
	q.push(s);
	while(!q.empty()){
		int x = q.front();
		q.pop();
		for(int i = fir[x];i;i = edge[i].next){
			if(dis[edge[i].to] != 0)continue;
			f[edge[i].to][0] = x;
			dis[edge[i].to] = dis[x] + 1;
			for(int j = 1;j <= t;j++){
				f[edge[i].to][j] = f[f[edge[i].to][j - 1]][j - 1];
			}
			q.push(edge[i].to);
		}
	}
	return;
}
int lca(int x,int y){
	if(dis[x] < dis[y])swap(x,y);
	if(x == y)return x;
	for(int i = t;i >= 0;i--){
		if(dis[f[x][i]] >= dis[y])x = f[x][i];
	}
	if(x == y)return x;
	for(int i = t;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++){
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	t = (int)(log(n) / log(2)) + 1;
	bfs();
	for(int i = 1;i <= m;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		printf("%d\n",lca(x,y));
	}
	return 0;
}

結束