最近公共祖先(tarjan-塔杨算法)

O(m+n)——离线算法

如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

输入格式

第一行包含三个正整数 N,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。

接下来 N−1 行每行包含两个正整数 x,y,表示 x 结点和 y 结点之间有一条直接连接的边(数据保证可以构成树)。

接下来 M 行每行包含两个正整数 a,b,表示询问 a 结点和 b 结点的最近公共祖先。

输出格式

输出包含 M 行,每行包含一个正整数,依次为每一个询问的结果。

样例输入 #1

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

样例输出 #1

4
4
1
4
4

数据范围

对于 30% 的数据,N≤10,M≤10。

对于 70% 的数据,N≤10000,M≤10000。

对于 100% 的数据,N≤500000,M≤500000。

样例解释

第一次询问:2,4 的最近公共祖先,故为 4。

第二次询问:3,2 的最近公共祖先,故为 4。

第三次询问:3,5 的最近公共祖先,故为 1。

第四次询问:1,2 的最近公共祖先,故为 4。

第五次询问:4,5 的最近公共祖先,故为 4。

故输出依次为 4,4,1,4,4。


 

代码长度限制

16 KB

时间限制

3000 ms

内存限制

512 MB

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
 const int N=5e5;
  void tarjan(int u);
 vector<int> e[N];
 int n,m,s;
 vector< pair<int,int> > query[N];
 
 int fa[N];
 bool vis[N];
 int ans[N];
 int find(int u)
 {
 	if(u==fa[u]) return u;
 	return fa[u]=find(fa[u]);
 }
 
 void tarjan(int u)
 {
 	vis[u]=true;
 	for(vector<int>::iterator it= e[u].begin();it!=e[u].end();it++)
 	{
 		int v=*it;
 		if(!vis[v])
 		{
 			tarjan(v);
 			fa[v]=u;
		 }
	 }
	 pair<int ,int> q;
	 for(vector< pair<int,int> > ::iterator it=query[u].begin();it!=query[u].end();it++)
	 {
	 	q=*it;
	 	int v=q.first,i=q.second;
	 	if(vis[v])
	 	ans[i]=find(v);
	 }
	 
 }
 
int main() 
{
	cin>>n>>m>>s;
	int a,b;
	for(int i=1;i<n;i++)
	{
		scanf("%d%d",&a,&b);
		e[a].push_back(b);
		e[b].push_back(a); 
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&a,&b);
		query[a].push_back({b,i});
		query[b].push_back({a,i});
	}
	for(int i=1;i<=N;i++)
	{
		fa[i]=i;
	}
	memset(vis,false,sizeof vis);
  tarjan(s);
	for(int i=1;i<=m;i++)
	{
		cout<<ans[i]<<endl;
	}
	
	
	return 0;
}