OI學習筆記之倍增求LCA

倍增求LCA


一,首先回顧一下什麼是倍增算法,倍增算法就是改善一下一步一步跳的緩慢,改爲跳2^k 步從而達到加快速度的目的,倍增算法一般要先預處理一個數組,代表從從某個點開始跳2^k 個數到達哪裏,比如ST表的ST[i][j]代表從第i個數向後2^j 個數,樹上倍增求LCA的f[i][j]表示i的第2^j 個祖先是誰。


二,最近公共祖先LCA概念篇
1,祖先:與x處於同一條重鏈且深度小於點x的節點都成爲點想的祖先。
2,公共祖先:若給定一棵樹,結點z即是結點x的祖先,結點z也是結點y的祖先,那麼稱z是結點x和y的公共祖先。
3,最近公共祖先:結點x和結點y的所有祖先中深度最深的那個,記作LCA(x,y)。

在這裏插入圖片描述
比如在此圖中,5的祖先有1和2; 5和6的公共祖先有1和2; 5和6的最近公共祖先是2; 有沒有發現根節點是所有任意兩個節點的公共祖先,所以最近公共祖先一定存在,最差是根節點。


三,如何求解LCA
1,考慮如何暴力求解,如果兩個結點的深度相同,我們是不是只需要讓兩個結點同時一步一步往上走,即a走到它的父親的同時,b也走到它的父親,如果兩個點走到的結點不相同就證明當前節點不是a和b的最近公共祖先就繼續走,知道走到節點相同證明當前節點是點a和點b的最近公共祖先。
2,考慮如何優化1,1緩慢的原因是它在一步一步的向上走所以導致了算法的低效,如果我們可以大步向上走,每次在不超過範圍的情況下向上走2^k步再判斷是不是就加快了算法速度,爲了實現這個效果我們引入倍增算法
首先要預處理兩個數組Log(以2爲底的對數中整數最大的那個),Log[0]=-1,Log[i]=Log[i>>1]|1;F(i, j) 表示點i 向上走2j 步的結點,Fi,0 就是點i 的父親節點,F(i, j)= F((Fi, j−1), j−1)(從第i個點向上走2^j 個點肯定等於從i出發向上走2^(j-1) 個點再向上走2^(j-1)個節點。

算法流程:
將a 和b 調整到相同高度
a. 判斷a 和b 是否重合,若重合則該點即爲答案
b.令a 和b 一起向上移動儘可能大的距離,保證移動後兩點不重合
c. 此時兩點的父親結點即爲答案

代碼實現也並不難

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define maxn 500005

using namespace std;

struct node
{
	int ed,nxt;
};
node edge[maxn<<1];
int first[maxn],n,m,s,cnt,f[maxn][30],Log[maxn],dep[maxn]; 

inline void add_edge(int s,int e)
{
	cnt++;
	edge[cnt].ed=e;
	edge[cnt].nxt=first[s];
	first[s]=cnt;
	return;
}

inline void deal_first(int k,int fa)
{
	dep[k]=dep[fa]+1;
	f[k][0]=fa;
	for(int i=1;(1<<i)<=dep[k];i++)
	    f[k][i]=f[f[k][i-1]][i-1];
	for(int i=first[k];i;i=edge[i].nxt)
	{
		int e=edge[i].ed;
		if(e==fa) continue;
		deal_first(e,k);
	}
}

inline int Lca(int a,int b)
{
	if(dep[a]<dep[b]) swap(a,b);
	for(int i=Log[dep[a]];i>=0;i--)
	    if(dep[f[a][i]]>=dep[b]) a=f[a][i];
	if(a==b) return a;
	for(int k=Log[dep[a]];k>=0;k--)
	    if(f[a][k]!=f[b][k])
	       a=f[a][k],b=f[b][k];
	return f[a][0];
}

int main()
{
	scanf("%d%d%d",&n,&m,&s);
	for(int i=1;i<=n-1;i++)
	{
		int s,e;
		scanf("%d%d",&s,&e);
		add_edge(s,e);
		add_edge(e,s);
	}
	dep[s]=1;
	deal_first(s,0);
	Log[0]=-1;
	for(int i=1;i<=n;i++)
	    Log[i]=Log[i>>1]+1;
	for(int i=1;i<=m;i++)
	{
		int a,b;
		scanf("%d%d",&a,&b);
		printf("%d\n",Lca(a,b));
	}
	return 0;
}

寫在最後,祝各位OIER NOIP2019 RP++ SCORE++

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章