最近公共祖先(倍增LCA)

题目

最近公共祖先

描述

给定一棵树,请查询结点u和v的最近公共祖先。最近公共祖先,就是两个节点在这棵树上深度最大(离根结点最远)的公共的祖先节点,结点的祖先也可以是自身。

输入

输入数据第一行为结点个数n(n<=900),接下来有n行,每行格式如下:
x m y1, y2, ... ym
表示结点x有m个孩子y1, y2, ... ym
接下来有一个正整数q(q<=100000),表示查询次数,之后有q行,每行两个正整数u和v,表示待查寻的结点编号。
树结点的编号为1~n。

输出

对每次查询,输出u和v的最近公共祖先,格式为:
u v = w
其中w表示u和v的最近公共祖先结点编号。

样例输入

5
4 0
5 3 1 4 2
1 0
2 1 3
3 0
6
1 5
1 4
4 2
2 3
1 3
4 3

样例输出

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

代码

#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f 
const int N=1100;
int fa[N][N],depth[N];
int h[N],e[N*2],ne[N*2],idx;  //链式前向星无向,边数得乘2
int d[N],root;
void add(int x,int y)    //链式前向星建边
{
	e[idx]=y;
	ne[idx]=h[x];
	h[x]=idx++;
}
void bfs(int u)  //广搜预处理fa数组和depth数组
{ 
	memset(depth,0x3f,sizeof(depth));
	depth[0]=0,depth[root]=1;
	queue<int>q;
	q.push(root);
	while(!q.empty()) {
		int t=q.front();
		q.pop();
		for(int i=h[t];i!=-1;i=ne[i]) {
			int j=e[i];
			if(depth[j]>depth[t]+1) {     //广搜预处理depth数组
				depth[j]=depth[t]+1;
				q.push(j);
				fa[j][0]=t;    //fa[j][0]表示父亲
				for(int k=1;k<=15;k++) {
					fa[j][k]=fa[fa[j][k-1]][k-1]; //f(i,k)=f(f(i,k-1),k-1)推导处理fa数组
				}
			}
		}
	}
}
int lca(int a,int b)
{
	if(depth[a]<depth[b]) swap(a,b);         //如果a比b浅,交换a,b
	for(int i=15;i>=0;i--) {                 //1.将a和b调到同一层
		if(depth[fa[a][i]]>=depth[b])
			a=fa[a][i];
	}
	if(a==b) return a;                       //如果调换以后相等则b为最近祖先
	for(int i=15;i>=0;i--) {                 //否则a和b一起向上跳,一直到最近祖先的儿子结点
		if(fa[a][i]!=fa[b][i]) {
			a=fa[a][i];
			b=fa[b][i];
		}
	}
	return fa[a][0];
}
int main()
{
	int n;
	cin>>n;
	memset(h,-1,sizeof(h));
	for(int i=1;i<=n;i++) {
		int x,m;
		cin>>x>>m;
		while(m--) {
			int xx;
			cin>>xx;
			d[xx]++;
			add(x,xx);
			add(xx,x);
		}
	}
	for(int i=1;i<=n;i++) if(d[i]==0) root=i;   //入度为0的点作为根结点
	// cout<<root<<endl;
	bfs(root);
	int m;
	cin>>m;
	while(m--) {
		int a,b;
		cin>>a>>b;
		int t=lca(a,b);
		cout<<a<<" "<<b<<" = "<<t<<endl;
	}
	// system("pause");
	return 0;
}
posted @ 2022-07-21 18:50  海盐味的苏打水  阅读(69)  评论(2编辑  收藏  举报