1. 程式人生 > >LCA的各類解法(三)——tarjan求LCA

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

一.概述.

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;
}