天天看點

最近公共祖先(LCA)

題目描述

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

輸入格式

第一行包含三個正整數 N,M,S,分别表示樹的結點個數、詢問的個數和樹根結點的序号。

接下來 N−1 行每行包含兩個正整數 x,y,表示 x 結點和 y 結點之間有一條直接連接配接的邊(資料保證可以構成樹)。

接下來 M 行每行包含兩個正整數 a,b,表示詢問 a 結點和 b 結點的最近公共祖先。

輸出格式

輸出包含 M 行,每行包含一個正整數,依次為每一個詢問的結果。

輸入輸出樣例

輸入 #1

5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
           

輸出 #1

4
4
1
4
4
           

說明/提示

對于 30% 的資料,N≤10,M≤10。

對于 70% 的資料,N≤10000,M≤10000。

對于 100% 的資料,N≤500000,M≤500000。

視訊詳解:最近公共祖先(LCA)問題_哔哩哔哩_bilibili

算法一:樸素算法1(70分)

dfs求每個節點的父節點,然後lca找最近公共子節點!!!

#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
int N,M,S;
bool vis[500005];
int deep[500005];//多餘
int father[500005];
vector<int>head[500005];
int k=0;
void dfs(int s){
	for(int i=0;i<head[s].size();i++){
		vis[s]=true;
		int v=head[s][i];
		if(!vis[v]){
			father[v]=s;
			deep[v]=deep[s]+1;
			dfs(v);
		}
		
	}
}
int lca(int x,int y){
	memset(vis,false,sizeof(vis));
	while(x){
		vis[x]=true;
		x=father[x];
	}
	while(y){
		if(vis[y]) return y;
		y=father[y];
	}
}
int main(){
	scanf("%d%d%d",&N,&M,&S);
	for(int i=1;i<N;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		head[x].push_back(y);
		head[y].push_back(x);
	}
	deep[S]=0;
	father[S]=0;
	vis[S]=true;
	dfs(S);
	for(int i=0;i<M;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		printf("%d\n",lca(x,y));
	}
}
           

算法二:樸素算法2(100分)

dfs求每個節點的深度和父節點,然後lca找最近公共子節點!!!

#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
int N,M,S;
bool vis[500005];
int deep[500005];
int father[500005];
vector<int>head[500005];
int k=0;
void dfs(int s){
	for(int i=0;i<head[s].size();i++){
		vis[s]=true;
		int v=head[s][i];
		if(!vis[v]){
			father[v]=s;
			deep[v]=deep[s]+1;
			dfs(v);
		}
		
	}
}
int lca(int x,int y){
	if(deep[x]<deep[y]) swap(x,y);
	while(deep[x]>=deep[y]){
		//cout<<x<<' '<<y<<endl;
		if(deep[x]==deep[y]){
			if(x==y) return x;
			if(father[x]==father[y]) return father[x];
		}
		if(deep[x]!=deep[y]) x=father[x];
		else{
			x=father[x];y=father[y];
		}
	}
	//memset(vis,false,sizeof(vis));
	/*while(x){
		vis[x]=true;
		x=father[x];
	}
	while(y){
		if(vis[y]) return y;
		y=father[y];
	}*/
}
int main(){
	scanf("%d%d%d",&N,&M,&S);
	for(int i=1;i<N;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		head[x].push_back(y);
		head[y].push_back(x);
	}
	deep[S]=0;
	father[S]=0;
	vis[S]=true;
	dfs(S);
	for(int i=0;i<M;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		printf("%d\n",lca(x,y));
	}
}
           

算法三:倍增法

倍增算法可以線上求樹上兩個點的LCA,時間複雜度為O(nlogn) 。

#include<iostream>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
using namespace std;
int N,M,S;
bool vis[500005];
int deep[500005];
int father[500005];
vector<int>head[500005];
int f[500005][30]; 
int k=0;
void dfs(int s){
	for(int i=0;i<head[s].size();i++){
		vis[s]=true;
		int v=head[s][i];
		if(!vis[v]){
			f[v][0]=s;
			deep[v]=deep[s]+1;
			dfs(v);
		}
		
	}
}
int lca(int x,int y){
	if(deep[x]<deep[y]) swap(x,y);
	for(int i=log2(deep[x]-deep[y]);i>=0;i--){
		if(deep[x]-deep[y]>=(1<<i)){
			x=f[x][i];
		}
	}
	if(x==y) return x;
	for(int i=log2(deep[x]);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);
		head[x].push_back(y);
		head[y].push_back(x);
	}
	deep[S]=0;
	father[S]=0;
	vis[S]=true;
	dfs(S);
	for(int i=1;i<=22;i++){
		for(int u=1;u<=N;u++){
			f[u][i]=f[f[u][i-1]][i-1];
		}
	}
	for(int i=0;i<M;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		printf("%d\n",lca(x,y));
	}
}
           

繼續閱讀