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;
}
上一篇:最强连通分量(Tarjan)笔记


下一篇:Tarjan