[笔记]LCA最近公共祖先---倍增在线算法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_27121257/article/details/78016948

一.定义:(出自百度百科)

对于有根树T的两个结点u、v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u、v的祖先且x的深度尽可能大。

二.在线算法(同上):

以序列化的方式一个个的处理输入,也就是说在开始时并不需要已经知道所有的输入。

即在可以在原树改动的情况下随时查询

三.基本思路
位运算 1 < < x 1< < x 等价于  1 × 2 x 1\times2^x
1.预处理(倍增思想)
 令 f a [ x ] [ y ] fa[x][y] 为x的 2 y 2^y 的祖先
 初始化: f a [ x ] [ 0 ] = f a t h e r [ x ] fa[x][0]=father[x]
 转移方程:
   f a [ x ] [ y ] = f a [ f a [ x ] [ y 1 ] ] [ y 1 ] fa[x][y]=fa[ fa[x][y-1] ][ y-1 ]
 {与RMQ类似:  2 j = 2 j 1 + 2 j 1 2^j=2^{j-1} +2^{j-1}

inline void get_lca()
{
	int k=get_k(n);// log2(n)
	for(int j=1;j<=k;++j)
	for(int i=1;i<=n;++i)
	if(fa[i][j-1])//终点 不可省略
	    fa[i][j]=fa[fa[i][j-1]][j-1];
}

2.计算 log 2 ( x ) \log_{2}(x)  {可无视}
 ps:1.别用cmath函数,不是一般的慢
  2.其实可以直接先用计算机试出来 log 2 ( n ) \log_2(n) ,然后一直用它

inline int get_k(int x)
{
	int k;
	for(k=1;(1<<k)<=x;++k);
	return k-1; //记得-1
}

也可打表或直接预处理出 1 n 1-n log \log

for(int i=1;i<=n;++i)
  lg[i]=lg[i-1]+(1<<(lg[i-1]+1)==i);

3.查询

inline int query(int x,int y)
{
	if(deep[x]<deep[y]) swap(x,y); //默认右边深度大
	int k=get_k(deep[x]);//其实也可以直接用max_k
	int t=deep[x]-deep[y];
	for(int i=0;(1<<i)<=t;++i)//注意限定条件
	if((1<<i)&t)//利用二进制 5(101)则相应的i=0与i=2时,x才向上跳
	    x=fa[x][i];	
	if(x==y) return x;//特殊情况
	for(int i=k;i>=0;--i)//依次缩小跳的幅度
	if(fa[x][i]!=fa[y][i] && fa[x][i])//注意fa[x][i]的存在问题
	{
		x=fa[x][i];
		y=fa[y][i];
	}
	return fa[x][0];//注意lca(x,y)为 fa[x][0]\fa[y][0]
}

PS:利用 log \log 表,可进行相应简化(跳高度){ 参考luogu的讲义 }

for(;deep[x]>deep[y];)
    x=f[x][lg[deep[x]-deep[y]]];

以及本人根据lowbit而写的简化(详情见树状数组,求2进制下末位的1)

	for(int t = dep[x] - dep[y]; t; t -= t & (-t))
		x = fa[x][lg[t & (-t)]];

四.模板

#include <cstdio>
#include <cstdlib>
#include <cstring>
#define open(s) freopen(s".in","r",stdin); freopen(s".out","w",stdout);
#define close fclose(stdin); fclose(stdout); 
using namespace std;

struct Edge
{
	int to;
	int next;
};

int n,m;
int cnt;
int deep[500005];
int head[500005];
int fa[500005][20]; //max: log2(n)+1 一条链(极端)
Edge edge[1000005];

inline int read()
{
	int k=1;
	int sum=0;
	char c=getchar();
	for(;'0'>c || c>'9' ;c=getchar())
		if(c=='-') k=-1;
	for(;'0'<=c && c<='9';c=getchar())
		sum=sum*10+c-'0';
	return sum*k;
}

inline void write(int x)
{
	if(x<0) { putchar('-'); x*=-1; }
	if(x>9) write(x/10);
	putchar(x%10+'0');
}

inline void add(int x,int y)
{
    ++cnt;
	edge[cnt].to=y;
	edge[cnt].next=head[x];
	head[x]=cnt;
}

inline void dfs(int pre,int p)
{
	deep[p]=deep[pre]+1;
	fa[p][0]=pre;
	for(int i=head[p];i;i=edge[i].next)
	if(edge[i].to!=pre && !deep[edge[i].to])
		dfs(p,edge[i].to);
}

inline void swap(int &x,int &y)
{
	int tmp=x; x=y; y=tmp;
}

inline int get_k(int x)
{
	int k;
	for(k=1;(1<<k)<=x;++k);
	return k-1;
}

inline void get_lca()
{
	int k=get_k(n);
	for(int j=1;j<=k;++j)
	for(int i=1;i<=n;++i)
	if(fa[i][j-1])
		fa[i][j]=fa[fa[i][j-1]][j-1];
}

inline int query(int x,int y)
{
	if(deep[x]<deep[y]) swap(x,y);
	int k=get_k(deep[x]);
	int t=deep[x]-deep[y];
	for(int i=0;(1<<i)<=t;++i)
	if((1<<i)&t)
	    x=fa[x][i];	
	if(x==y) return x;
	for(int i=k;i>=0;--i)
	if(fa[x][i]!=fa[y][i] && fa[x][i])
	{
		x=fa[x][i];
		y=fa[y][i];
	}
	return fa[x][0];
}

int main()
{
	open("3367");

    n=read();
	m=read();
	int x1=read();
    for(int i=1;i<n;++i)
	{
		int x=read(),y=read();
		add(x,y);
		add(y,x);
	}
	dfs(0,x1);
	get_lca();
    for(int i=1;i<=m;++i)
	{
		int x=read(),y=read();
		write(query(x,y));
		putchar('\n');
	}
	
    close;
	return 0;
}


猜你喜欢

转载自blog.csdn.net/qq_27121257/article/details/78016948