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\)时:
- 标记当前节点为已访问节点。
- 递归遍历其所有子节点\(y\),并用并查集合并。
- 遍历与当前与当前节点有查询关系的结点(称之为\(z\))(即是需要查询LCA的另一些结点),如果\(z\)已经访问,那么\(x\)与\(z\)的LCA就是\(cz(z)\)(查找函数),输出或者记录下来就可以了。
代码:
#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;
}