1. 程式人生 > >兩種特別有用的求LCA的方法

兩種特別有用的求LCA的方法

第一種:樹鏈剖分@TOC
一:知識儲備

  1. 重節點:以i為根的節點中結點數最多的結點
  2. 輕結點:其他結點
  3. 重鏈:由重節點連成的鏈

二:實現必需品:

  1. dep存深度
  2. son存重節點是誰
  3. siz存以i為根的子樹大小
  4. fa存父親是誰
  5. top存“頭”
    ~重節點的頭是他所在鏈的第一個出現的點
    ~輕結點的頭是他自己

三:程式碼:

#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int maxn=100; 
//siz是它對應的結點的子樹個數,算上他自己
//son儲存這個點的重兒子 
struct Edge{
	int to;//下一個點 
	int val;//邊權 
};
//這裡使用鄰接表存圖,和鏈式前向星一樣 
vector<Edge>g[maxn];
int fa[maxn],siz[maxn],son[maxn],dep[maxn];
int top[maxn];
//求出每個點的重兒子 
void dfs1(int u){
	siz[u]=1;
	son[u]=0;
	for(int i=0;i<g[u].size();i++){//迴圈邊 
		Edge &v=g[u][i];
		if(fa[u]==v.to)continue;
		dep[v.to]=dep[u]+1;
		fa[v.to]=u;
		dfs1(v.to);
		if(siz[son[u]]<siz[v.to])son[u]=v.to;
		siz[u]+=siz[v.to];
	}
}
//若他在一條重鏈上,他的top就是第一個出現的點
//反之,他的top就是他自己 
//求每個點的top 
void dfs2(int u,int tp){
	top[u]=tp;
	if(son[u])dfs2(son[u],tp);//走重鏈 
	for(int i=0;i<g[u].size();i++){
		Edge &v=g[u][i];
		if(v.to==son[u]||v.to==fa[u])continue;
		//若是一個重兒子,因為已經走過就不走了
		//若是父親當然也不走 
		dfs2(v.to,v.to);
	}
} 
int lca(int u,int v){
	int fu=top[u];
	int fv=top[v];
	while(fu!=fv){
		if(dep[fu]<dep[fv]){
			swap(fu,fv);
			swap(u,v);
		}
		//跳的是深度較深的 
		u=fa[fu];
		fu=top[u];
	}
	if(dep[u]<dep[v])swap(u,v);
	//返回的是深度小的那個
	//因為他們這時的top相同,說明在一條重鏈上,深度較淺的那個是lca 
	return v;
}
int main() {
	int n;
	scanf("%d",&n);
	for(int i=0;i<n-1;i++){
		int u,v,val;
		scanf("%d%d%d",&u,&v,&val);
		g[u].push_back((Edge){v,val});
		g[v].push_back((Edge){u,val});
	} 
	dep[1]=1;
	dfs1(1);
	cout<<"a";
	dfs2(1,1);
	cout<<"b";
	while(1){
		int u,v;
		cin>>u>>v;
		cout<<lca(u,v);
	}
	return 0;
}

第二種:倍增求LCA
一:實現必需品

  1. dp儲存這個點向上跳2的k次方步之後的點,這個點的祖先
  2. dep儲存點的深度

二:實現步驟

  1. 首先把深度預處理出來
  2. 把兩個點的深度跳到相同,再一起往上跳,直到跳到相同深度
  3. 求LCA

三:程式碼

#include <iostream>
int dp[maxn][20];
int dep[maxn];
//首先把深度跳到相同,再一起往上跳 ,直到跳相同的深度 
void dfs(int u,int fa,int h){
   dep[u]=h;
   int t=0;
   while(dp[u][t]){
   	dp[u][t+1]=dp[dp[u][t]][t];
   	t++;
   }
   for(int i=0;i<g[u].size();i++){
   	int now=g[u][i];
   	if(now==fa)continue;
   	dp[now][0]=u;
   	dfs(now,u,h+1);
   }
}
int lca(int u,int v){
   if(dep[u]<dep[v])swap(u,v);
   //先跳到相同節點 
   for(int i=19;i>=0;i--){
   	//根節點的深度要為1,不然他們可能跳過頭 
   	if(dep[dp[u][i]]>=dep[v]){
   		u=dp[u][i];
   	}
   }
   if(u==v)return u;
   for(int i=19;i>=0;i--){
   	if(dp[u][i]^dp[v][i])u=dp[u][i],v=dp[v][i];
   	//兩個相同的數異或起來等於0 
   	//看他們的公共祖先 
   }
   return dp[u][0];
}
int main() {
   scanf("%d%d",&n,&m);
   for(int i=0;i<n-1;i++){
   	int a,b;
   	scanf("%d%d",&a,&b);
   	g[a].push_back(b);
   	g[b].push_back(a);
   }
   return 0;
}