1. 程式人生 > >[Tarjan演算法]最近公共祖先(LCA)問題求解

[Tarjan演算法]最近公共祖先(LCA)問題求解

想了一想幾個月前打的用於解LCA的Tarjan貌似棄坑就沒再管它,然後虛擬機器磁碟被我莫名其妙起爆了以後之前打的程式全都打了水漂就想起了被置之不理的Tarjan解LCA問題的板子,索性就把坑填上唄,畢竟我不是挖坑不填的主 明明還有一堆亂七八糟的平衡樹沒填
LCA就是樹上兩點的最近公共祖先。說這個之前,得先了解一下什麼是樹上兩點的公共祖先。
這是一棵樹
就比如上圖中根節點為t[1]的樹,在其上的節點t[4]和t[5]有兩個公共祖先,一個是它們共有的父親t[3],一個是它們共有的父親的父親t[1]。如果深度大一點,從它們最開始共有的祖先,即最近公共祖先(LCA)及它們的LCA的所有祖先都是它們的公共祖先。自然,根節點是以它為根的樹上任意兩點的公共祖先。
然而求公共祖先並沒有什麼用。我們所要的只是它們的LCA而已,也就是它們的公共祖先中深度最大的那個節點。線上演算法有ST,不過在這裡我求LCA用的演算法是離線的Tarjan。
Tarjan演算法是建立在一個深度優先搜尋(DFS)上的,它把每一個點對的問題都統計完後通過對整棵樹一遍DFS解決所有問題然後輸出結果。這也是它較暴力演算法時間複雜度大幅降低的原因。
在通過鄰接表的方式(畢竟你也不知道它是幾叉樹)把樹儲存完以後,我們把要求求LCA的所有點對連線起來(自然也是鄰接表儲存噠)做出另一個圖,就像紅色線所標識的那樣:
這還是一棵樹


這樣子我們就有了一個問題集合的無向圖。然後我們就可以從根節點開始遍歷整棵樹,圖上就是從t[1]開始。途中用f[x]的方式記錄當前情況下x所在集合的代表,這個可以用並查集解決。
在DFS到t[x]時我們先初始化f[x]=x,並令vis[x]=true。按照問題的鄰接表查詢一下含有t[x]點的每個問題,如果點對中另一個點t[y]已經走過了(可以開個bool型別的vis陣列解決此問題),那就把相應標號的問題寫上解為find(y)(就是並查集find()操作啦),否則不予置理。之後我們遍歷一遍與t[x]相連的樹上的所有邊,只要與t[x]相連的點t[z]的vis[z]=false,t[z]就必定是t[x]的兒子之一,我們就可以遞迴再搜尋一遍t[z],搜尋完後令f[z]=x,直到遍歷完整棵樹。
因為在把整棵樹遍歷的過程中,我們手推一下就能知道以節點t[x]為根的樹中所有節點都遍歷過之前,f[x]一定是x,所以可以得出只要是已經能解決的要求就必定是最近的公共祖先的結論。LCA問題也就解決了。
最後例行上程式碼:

#include<cstdio>
#define maxn 500005
#define maxm 1000001
int f[maxn],t[maxn],nx[maxm],v[maxm],a[maxm<<1];
int T[maxn],Nx[maxm],V[maxm],ans[maxn],h;
int n,m,s;bool vis[maxn];
int find(int x){
    if(f[x]!=x)f[x]=find(f[x]);
    return f[x];
}
void dfs(int r){int k;vis[r]=1;f[r]=r;
    k=t[r];while
(k){ if(vis[v[k]])ans[a[k]]=find(v[k]); k=nx[k]; }k=T[r];while(k){ if(!vis[V[k]])dfs(V[k]),f[V[k]]=r; k=Nx[k]; } } int main(){int i,b,e; scanf("%d%d%d",&n,&m,&s); for(i=1;i<n;i++){ scanf("%d%d",&b,&e); Nx[++h]=T[b],T[b]=h,V[h]=e; Nx[++h]=T[e],T[e]=h,V[h]=b; }for(h=0,i=1;i<=m;i++){ scanf("%d%d",&b,&e); nx[++h]=t[b],t[b]=h,v[h]=e,a[h]=i; nx[++h]=t[e],t[e]=h,v[h]=b,a[h]=i; }dfs(s); for(i=1;i<=m;printf("%d\n",ans[i]),i++); }

The End

相關推薦

[Tarjan演算法]最近公共祖先(LCA)問題求解

想了一想幾個月前打的用於解LCA的Tarjan貌似棄坑就沒再管它,然後虛擬機器磁碟被我莫名其妙起爆了以後之前打的程式全都打了水漂就想起了被置之不理的Tarjan解LCA問題的板子,索性就把坑填上唄,畢竟我不是挖坑不填的主 明明還有一堆亂七八糟的平衡樹沒填 LC

用於求最近公共祖先(LCA)的 Tarjan演算法–以POJ1986為例(轉)

給定有向無環圖(就是樹,不一定有沒有根),給定點U,V,找出點R,保證點R是U,V的公共祖先,且深度最深;或者理解為R離這兩個點的距離之和最小.如何找出R呢? 最一般的演算法是DFS(DFS本是深度優先搜尋,在這裡姑且把深度優先遍歷也叫做DFS,其實是

最近公共祖先LCA:Tarjan演算法

1,並查集+dfs 對整個樹進行深度優先遍歷,並在遍歷的過程中不斷地把一些目前可能查詢到的並且結果相同的節點用並查集合並. 2,分類,使每個結點都落到某個類中,到時候只要執行集合查詢,就可以知道結點的LCA了。 對於一個結點u.類別有: 以u為根的子樹、除類一以外的以f

【C++】最近公共祖先LCATarjan離線算法)&& 洛谷P3379LCA模板

target sizeof add 例題 開始 實現 再看 根節點 strong 1.前言   首先我們介紹的算法是LCA問題中的離線算法-Tarjan算法,該算法采用DFS+並查集,再看此算法之前首先你得知道並查集(盡管我相信你如果知道這個的話肯定是知道並查集的),

最近公共祖先LCA---線上倍增演算法

  線上求LCA,多次詢問。倍增演算法時間複雜度為。   1、dfs求每個節點所在層數 void dfs(int u,int root,int d) { int i; depth[u]=d; fa[u][0]=root;//初始化 int

hdu 2586How far away ? 最近公共祖先LCA離線演算法

There are n houses in the village and some bidirectional roads connecting them. Every day peole always like to ask like this "How far is

最近公共祖先LCA模板(Tarjan/RMQ)

Description Solution 每次想陣列名字都想的異常艱難,於是(因果關係?)這裡存一下模板 離線演算法 Tarjan #include<iostream&

HDU4547 CD操作(最近公共祖先lca,離線Tarjan,有坑點)

Problem Description  在Windows下我們可以通過cmd執行DOS的部分功能,其中CD是一條很有意思的命令,通過CD操作,我們可以改變當前目錄。   這裡我們簡化一下問題,假設只有一個根目錄,CD操作也只有兩種方式:  

演算法詳解之最近公共祖先(LCA)

若圖片出鍋請轉至here 概念 首先是最近公共祖先的概念(什麼是最近公共祖先?): 在一棵沒有環的樹上,每個節點肯定有其父親節點和祖先節點,而最近公共祖先,就是兩個節點在這棵樹上深度最大的公共的祖先節點。 換句話說,就是兩個點在這棵樹上距離最近的公共祖先節點。 所以LCA主要是用來處理當兩個點僅有唯一一條確定

[Tarjan] P3379 最近公共祖先

markdown namespace tar main roo can 現在 oid const 一直都沒有寫過tarjan版本的,現在要水一波。 原題:我是傳送門 題解: #include <cstdio> #include <cstring> u

[模板]最近公共祖先(LCA)

一段 post 大小 const fin cstring https string print https://www.luogu.org/problemnew/show/P3379 1 #include <bits/stdc++.h> 2 us

最近公共祖先--lca

tchar put def https () iostream max continue lca 模版題 https://www.luogu.org/problemnew/show/P3379 1 #include<algorithm> 2 #inc

最近公共祖先LCA詳細解法

最近公共祖先(LCA)是樹上倍增的一種典型例題。 問題描述:給定一棵具有n個節點的樹,詢問兩個節點x,y的最近的公共祖先。   分析:首先我們想到的是暴力演算法,當然這個演算法妥妥的會TLE掉,所以我們採用倍增優化的方法來進行實現,也就是我們今天介紹的重點。   所謂倍增,就是按2的倍數

最近公共祖先——LCA 總結及其運用

tarjan模板 #include<cstdio> #include<iostream> #include<cstring> #include<algor

最近公共祖先LCA 模板

程式碼如下: #include<cstdio> #include <iostream> #include <cstring> #include <cstdli

最近公共祖先 LCA

一、基本演算法        1、 Tarjan演算法基於深度優先搜尋的框架,對於新搜尋到的一個節點,首先建立有這個節點構成的集合,再對當前節點的每一個子樹進行搜尋,每搜尋完一個子樹,則確定子樹內的L

[模板] 最近公共祖先/lca

簡介 最近公共祖先 \(lca(a,b)\) 指的是a到根的路徑和b到n的路徑的深度最大的公共點. 定理. 以 \(r\) 為根的樹上的路徑 \((a,b) = (r,a) + (r,b) - 2 * (r,fa(lca))\). (樹上差分) 求法 tarjan 離線演算法, 總時間 \(O(n+

最近公共祖先(LCA)詳解

LCA問題(Least Common Ancestors,最近公共祖先問題),是指給定一棵有根樹T,給出若干個查詢LCA(u, v)(通常查詢數量較大),每次求樹T中兩個頂點u和v的最近公共祖先,即找一個節點,同時是u和v的祖先,並且深度儘可能大(儘可能遠離樹根)。 LCA

Tarjan離線演算法最近公共祖先LCA

轉自: arjan離線演算法求LCA介紹      前言:首先,本人搞懂Tarjan求最近公共祖先(LCA),也是瀏覽了大量其他網友大牛的文章,若是看了本文仍未弄懂的,可以嘗試自己做一下模板題(裸題)HDU2586,自己用資料去感受一下,或者可以換篇文章再看,或許他的

LCA最近公共祖先Tarjan演算法模板

#include <iostream> #include <cstdio> #include <cstring> #include <vector> using namespace std; /** 1.dfs 2.