数据结构——LCA小结

Posted konglingyi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构——LCA小结相关的知识,希望对你有一定的参考价值。

·

写法一、树上倍增

·倍增:以2^k为步来走

·说明:

Dep[v]记录节点v的深度(层数)
fa[v][k]记录节点v向上第2k个祖先的编号
·预处理:
fa[v][k]=fa[fa[v][k-1]][k-1];

·向上走(Go_up)的实现:

s为倍增的上限,比如这个树的总层数为8,则s为3(23==8)

从v号点向上走p层(伪代码)

Go_up(v,p) 

For(int i=0;i<S;i++)
 If(P&(1<<i)) v=fa[v][i];
                        // O(logN)  

此处利用奇妙的二进制与运算。举个例子来说明:

5的二进制:  1 0 1

         对应k:2 1 0

      对应2^k:4 0 1

显然,4+1=5.

i=0, (p&(1<<i))==(101&1)2==1 , v=fa[v][0]
i=1, (p&(1<<i))==(101&10)2==0
i=2, (p&(1<<i))==(101&100)2==1 , v=fa[v][2]

故通过这种写法,可以实现向上跳格子~

·LCA原理:

假设x是u和v的最近公共祖先
1.x往上到根的路径上的所有点都是u和v的公共祖先;
所以当fa[u][i]==fa[v][i]条件成立时,fa[u][i]不一定是它们的最近公共祖先。

2.若fa[u][i]!=fa[v][i],说明点fa[u][i]和点fa[v][i]一定是x下方的点。

求u和v公共祖先(伪代码)

Lca(u, v)  // dep[u]<dep[v]
Go_up(v,dep[v]-dep[u]);
For(int i=S;i>=0;i--)
If(fa[u][i]!=fa[v][i])  u=fa[u][i],v=fa[v][i]; 
Return fa[v][0];
 // O(logN)

·于是精妙且复杂的板子就可以搞出来了:

#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
const int maxn = 500005;
const int maxe = 1000005;
int n,m,root;

struct line
	int from,to;
	line()//空构造函数 line p; 
	line(int A,int B)
		//构造函数 line L=line(1,2);
		from=A;to=B;
	
;
line edge[maxe];
int last[maxn],_next[maxe],e; 

//last[x]表示以x为起点的最后一条边(的编号) 
//_next[i]表示与第i条边起点相同的上一条边(的编号) 

////////////////////////////////////////
void add_edge(int x,int y)
	edge[++e]=line(x,y);
	_next[e]=last[x];
	last[x]=e;

////////////////////////////////////////疑难处1 

int Fa[maxn][35],Dep[maxn];
void dfs(int x,int fa)
	int i,k,y;
	Fa[x][0]=fa;
	Dep[x]=Dep[Fa[x][0]]+1;                       //记录当前节点的深度 
	k=ceil(log(Dep[x])/log(2));                      //x往上倍增的上限 
	for(i=1;i<=k;i++)Fa[x][i]=Fa[Fa[x][i-1]][i-1];  //倍增计算祖先 
	
	///////////////////////////////////////////
	for(int i=last[x];i/*i>0*/;i=_next[i])
		int v=edge[i].to;
		if(v!=fa)dfs(v,x); 
	//////////////////////////////////////////疑难处2
	 
	

int LCA(int x,int y)
	int i,k,s;
	s=ceil(log(n)/log(2));                 //该树倍增最大可能的上限 
	if(Dep[x]<Dep[y])swap(x,y);      //交换x和y的值 
/////////////x往上走k层,让x与y处于同一层 //////////
	k=Dep[x]-Dep[y];
	
	/////////////////////////////////////////////////// 
	for(i=0;i<=s;i++)
	    if(k&(1<<i))x=Fa[x][i];//Go-up:k为进位,当k&(1<<i)==1时,说明向上走2^i步 
	//x==y时,x就是最近公共祖先
	///////////////////////////////////////////////////疑难处3 
	
	if(x==y)return x;                      
///////////////////////////////////////////////////
	s=ceil(log(Dep[x])/log(2));           //计算向上倍增的上限 
	for(i=s;i>=0;i--)
	    if(Fa[x][i]!=Fa[y][i]) x=Fa[x][i]; y=Fa[y][i]; 
	return Fa[x][0];


int main()
	int i,j,k;
	cin>>n>>m>>root;
	for(i=1;i<n;i++)
		int x,y;
		scanf("%d%d",&x,&y);
		add_edge(x,y);
		add_edge(y,x);
	
	dfs(root,0);
	for(i=1;i<=m;i++)
		int x,y;
		scanf("%d%d",&x,&y);
		printf("%d\\n",LCA(x,y));
	
		
 

·压轴—重要的图解说明:

技术图片


写法二、树链剖分

·树链剖分:

树链剖分,或者叫轻重链剖分,是一种把树变成链的方法.
size[]表示每个点的子树的大小.
dep[]表示每个点的深度.
Son[]表示每个点儿子中子树最大的那个儿子.
所有节点p和它的Son[p]连接的为重链,其他的边为轻链.
top[]表示每个点所在的重链的最上面的节点.
这几个数组可以通过两次dfs求出来.

 

现在之前的变量都求出来了,怎么求两个节点的LCA?
对于两个点x, y:
case1:他们的top相同,说明他们在同一个重链上,所以dep比较小的那个就是LCA.
case2:他们的top不同,则选择top深度比较深的那个往上跳.
时间复杂度:预处理为O(n),查询一次为O(logn).

·LCA代码:

void dfs(int p, int fa) 
	tr[p].sz = 1;
	for (int x = first[p]; x; x = e[x].next)
		if (e[x].to != fa) 
			int y = e[x].to;
			tr[y].dep = tr[p].dep + 1;
			dfs(y, p);
			tr[p].sz += tr[y].sz;
			if (tr[p].son == 0 || tr[y].sz > tr[tr[p].son].sz)
				tr[p].son = y;
		


void DFS(int p, int fa) 
	if (tr[p].son != 0) 
		tr[tr[p].son].top = tr[p].top;
		DFS(tr[p].son, p);
	
	for (int x = first[p]; x; x = e[x].next)
		if (e[x].to != fa && e[x].to != tr[p].son) 
			y = e[x].to;
			tr[y].top = y;
			DFS(y, p);
		


int lca(int x, int y) 
	while (tr[x].top != tr[y].top) 
		if (tr[tr[x].top].dep < tr[tr[y].top].dep)
			swap(x, y);
		x = tr[tr[x].top].fa;
	
	if (tr[x].dep < tr[y].dep) swap(x, y);
	return y;

  

以上是关于数据结构——LCA小结的主要内容,如果未能解决你的问题,请参考以下文章

LCA

圆方树小结

AC自动机小结

数据结构浅谈倍增求LCA

HDOJ 5296 Annoying problem LCA+数据结构

算法描述》LCA两三事(蒟蒻向)