tarjan法求LCA学习笔记

tarjan求LCA

前言

tarjan求LCA的时间复杂度是\(O(n+2*q)\),是非常优秀的复杂度,但缺点就是只能离线。(懂了,去学欧拉序\(O(1)\)求LCA)

tarjan求LCA需要用到并查集,本人用的代码:

int f[N];
void cz(int x){return x==f[x]?x:f[x]=cz(f[x]);}
void hb(int x,int y){f[cz(x)]=cz(y);}

tarjan查找到LCA后直接输出,如果无需输出,就要用数组先存储。

正文

思想

首先,树上两个点的LCA有两种可能,一种是其中的一个点,一种同是属于另一个点的子树内。
tarjan算法的思想是DFS遍历每一个节点,用并查集记录其之间的父子关系。

操作

当遍历到某个节点\(x\)时:

  1. 标记当前节点为已访问节点。
  2. 递归遍历其所有子节点\(y\),并用并查集合并。
  3. 遍历与当前与当前节点有查询关系的结点(称之为\(z\))(即是需要查询LCA的另一些结点),如果\(z\)已经访问,那么\(x\)\(z\)的LCA就是\(cz(z)\)(查找函数),输出或者记录下来就可以了。

代码:

LCA模板题

#include <bits/stdc++.h>
#define P push_back
#define M make_pair
using namespace std;
const int N=1e6;
int n,q,rt,vis[N],f[N],ans[N],cnt,fir[N];
vector <pair<int,int> >ask[N];
int cz(int x){return f[x]==x?x:f[x]=cz(f[x]);}
struct edge{int v,nt;}e[N<<1];void add(int u,int v){e[++cnt].v=v;e[cnt].nt=fir[u];fir[u]=cnt;}
void dfs(int u,int fa)
{
	vis[u]=1;f[u]=u;
	for(int i=fir[u];i;i=e[i].nt)
		if(e[i].v!=fa)
		{
			dfs(e[i].v,u);
			f[e[i].v]=u;
		}
	for(int i=0;i<ask[u].size();i++)
		if(vis[ask[u][i].second]==1)	ans[ask[u][i].first]=ask[u][i].second;
		else if(vis[ask[u][i].second])	ans[ask[u][i].first]=cz(ask[u][i].second);
	vis[u]=2;
}
int main()
{
	cin>>n>>q>>rt;for(int i=1;i<=n;i++)f[i]=i;
	for(int i=1,u,v;i<n;i++)
	{
		cin>>u>>v;
		add(u,v);add(v,u);
	}
	for(int i=1,x,y;i<=q;i++)
	{
		cin>>x>>y;
		ask[x].P(M(i,y));
		ask[y].P(M(i,x));
	}
	dfs(rt,0);for(int i=1;i<=q;i++)cout<<ans[i]<<endl;return 0;
}
posted @ 2022-02-08 07:47  AIskeleton  阅读(80)  评论(0编辑  收藏  举报