LCA的各类解法(三)——tarjan求LCA

版权声明:转载请注明原出处啦QAQ(虽然应该也没人转载): https://blog.csdn.net/hzk_cpp/article/details/83351201

一.概述.

tarjan求LCA是一种可以在O(n+m)的时间复杂度内处理离线LCA的方式.但是一旦遇到了在线询问,tarjan求LCA就失效了.

二.算法流程.

tarjan算法本质上是对朴素LCA的一个优化,它是基于dfs进行优化的.

我们考虑开一个n个vector,其中q[i]表示与i相关的询问点.

然后我们考虑一个dfs,当我们遍历到点x的时候,枚举与x相关的所有询问,当一个点y已经被遍历过的时候,显然y与x的LCA就是y的祖先中第一个被搜过但没有退出搜索的点.

为了维护这个过程,我们引入3种标记,其中0表示点未被搜过,1表示点被搜到但没有退出搜索,2表示点已经退出搜索.

之后当遍历一个点x时,枚举与x相关询问时,就可以让y从当前点开始往上爬,爬到的第一个标记为1的点即为这个询问的LCA.这个算法的时间复杂度最坏为O(mn).

为了优化这个算法,我们可以考虑使用并查集,当一个点被标记为1时,就创建一个以它为根的并查集;当一个点被标记为2时,就把它所处的并查集并到它的父亲上,这样就可以做到O(n+m).

至于为什么并查集的log没有算上,是因为这个并查集往上并的时候直接可以将根定为它的父亲,这个操作的复杂度为O(1),而get操作最多只会将整个并查集遍历一遍,时间复杂度之和最多为O(n).

三.模板题及代码.

题目:luogu3379.

代码如下:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
#define pb push_back
typedef long long LL;

int ri(){
  int x=0;
  char c;
  for (c=getchar();c<'0'||c>'9';c=getchar());
  for (;c<='9'&&c>='0';c=getchar()) x=x*10+c-'0';
  return x;
}

const int N=500000;

struct side{
  int y,next;
}e[N*2+9];
int n,m,lin[N+9],top,root;
vector<int>q[N+9],id[N+9];
int ans[N+9];
int fa[N+9],vis[N+9];

int get(int u){return fa[u]^u?fa[u]=get(fa[u]):u;}

void ins(int x,int y){
  e[++top].y=y;
  e[top].next=lin[x];
  lin[x]=top;
}

void dfs(int k){
  fa[k]=k;
  vis[k]=1;
  for (int i=lin[k];i;i=e[i].next)
    if (!vis[e[i].y]){
      dfs(e[i].y);
      fa[e[i].y]=k;
    }
  vis[k]=2;
  int len=q[k].size();
  for (int i=0;i<len;++i)
    if (vis[q[k][i]]==2) ans[id[k][i]]=get(q[k][i]);
}

Abigail into(){
  n=ri();m=ri();root=ri();
  int x,y;
  for (int i=1;i<n;++i){
    x=ri();y=ri();
    ins(x,y);ins(y,x);
  }
  for (int i=1;i<=m;++i){
    x=ri();y=ri();
    q[x].pb(y);id[x].pb(i);
    q[y].pb(x);id[y].pb(i);
  }
}

Abigail work(){
  dfs(root);
}

Abigail outo(){
  for (int i=1;i<=m;++i)
    printf("%d\n",ans[i]);
}

int main(){
  into();
  work();
  outo();
  return 0;
}

猜你喜欢

转载自blog.csdn.net/hzk_cpp/article/details/83351201