最近公共祖先 三(RMQ ST)

2021-08-28 03:47:02 字數 3178 閱讀 4799

上上回說到,小hi和小ho使用了tarjan演算法來優化了他們的「最近公共祖先」**,但是很快這樣乙個離線演算法就出現了問題:如果只有乙個人提出了詢問,那麼小hi和小ho很難決定到底是針對這個詢問就直接進行計算還是等待一定數量的詢問一起計算。畢竟無論是乙個詢問還是很多個詢問,使用離線演算法都是只需要做一次深度優先搜尋就可以了的。

那麼問題就來了,如果每次計算都只針對乙個詢問進行的話,那麼這樣的演算法事實上還不如使用最開始的樸素演算法呢!但是如果每次要等上很多人一起的話,因為說不准什麼時候才能夠湊夠人——所以事實上有可能要等上很久很久才能夠進行一次計算,實際上也是很慢的!

「那到底要怎麼辦呢?在等到10分鐘,或者湊夠一定數量的人兩個條件滿足乙個時就進行運算?」小ho想出了乙個折衷的辦法。

小ho面臨的問題還是和之前一樣:假設現在小ho現在知道了n對父子關係——父親和兒子的名字,並且這n對父子關係中涉及的所有人都擁有乙個共同的祖先(這個祖先出現在這n對父子關係中),他需要對於小hi的若干次提問——每次提問為兩個人的名字(這兩個人的名字在之前的父子關係中出現過),告訴小hi這兩個人的所有共同祖先中輩分最低的乙個是誰?

「那你快教我啊!」小ho耐不住性子。

「不要急,且聽我緩緩道來,還記得很久之前我和你說過的最近公共祖先其實就是這兩個點連通路徑上的那個折點麼(參見hiho一下第十一周樹的直徑)」小hi問道。

「記得!」

「這個折點也就是這2點所連路徑上深度最小的那個點了!那麼這個問題其實和我們之前所提到的那個求區間最小值的是不是差不多(參見hiho一下第十六周——rmq-st演算法),只不過乙個是在陣列上的區間,乙個是在樹上的區間?」小hi問道。

「你非要這麼說那我只能說是啦。。但是樹和陣列還是差了挺遠的吧。」小ho表示汗顏。

小hi點了點頭,隨即道:「那就這麼弄一下,我從樹的根節點開始進行深度優先搜尋,每次經過某乙個點——無論是從它的父親節點進入這個點,還是從它的兒子節點返回這個點,都按順序記錄下來。這樣,是不是就把一棵樹轉換成了乙個陣列?而找到樹上兩個節點的最近公共祖先,無非就是找到這兩個節點最後一次出現在陣列中的位置所囊括的一段區間中深度最小的那個點?

小ho顯然是沒有料到小hi還有這一招,一上來也是感覺明顯就不對嘛,畢竟好好的樹怎麼隨便就弄成陣列了不是,但是靜下心來仔細想想:「從第乙個點離開(返回它的父親節點),到從第二個點離開(返回它的父親節點)的這一段路程,的確經過的深度最小的點就是『最近公共祖先』這乙個點!」

看著小ho露出了驚訝的神情,小hi滿意的點了點頭,道:「這就是乙個很好的將樹轉換成陣列來進行某些特殊演算法的方法!而且你仔細看看就會發現轉換出的陣列的長度其實就是邊數的2倍而已,也是o(n)的級別呢~」

「原來是這樣!那這次我只需要簡單的套用之前寫的演算法,很簡單嘛!」小ho笑道。

「那是自然,你也不看看之前我們積累了乙個月呢,現在你要是還磨磨蹭蹭的,回國怎麼向河蟹先生交代!」

「嘿嘿嘿……」

close

輸入每個測試點(輸入檔案)有且僅有一組測試資料。

每組測試資料的第1行為乙個整數n,意義如前文所述。

每組測試資料的第2~n+1行,每行分別描述一對父子關係,其中第i+1行為兩個由大小寫字母組成的字串father_i, son_i,分別表示父親的名字和兒子的名字。

每組測試資料的第n+2行為乙個整數m,表示小hi總共詢問的次數。

每組測試資料的第n+3~n+m+2行,每行分別描述乙個詢問,其中第n+i+2行為兩個由大小寫字母組成的字串name1_i, name2_i,分別表示小hi詢問中的兩個名字。

對於100%的資料,滿足n<=10^5,m<=10^5, 且資料中所有涉及的人物中不存在兩個名字相同的人(即姓名唯一的確定了乙個人),所有詢問中出現過的名字均在之前所描述的n對父子關係中出現過,且每個輸入檔案中第乙個出現的名字所確定的人是其他所有人的公共祖先

輸出對於每組測試資料,對於每個小hi的詢問,按照在輸入中出現的順序,各輸出一行,表示查詢的結果:他們的所有共同祖先中輩分最低的乙個人的名字。

sample input

4

adam sam

sam joey

sam micheal

adam kevin

3sam sam

adam sam

micheal kevin

sample output

sam

adam

adam

對於這個題,它的核心部分其實就是通過找到每乙個區間的深度最小值,然後把它的編號(第幾次出現)寫入dp陣列。

當在lca函式裡面呼叫query函式的時候,因為之前已經通過rmq-st求得了 所有已知的區間的深度最小值所對應的編號(第幾次出現),所以query函式就可以找到dp陣列裡面存的深度最小值對應的編號(第幾次出現,不再一一具體指出)。

既然已知該區間深度最小值對應的編號,然後就可以通過之前深蒐時寫下的編號陣列f找到對應的點,該點通過map已經存下了字串和整型數對應的關係,進而也就找到了解。

陣列f裡面存的是第幾次出現了哪個點。

這就是這段**的核心思想,至於rmq-st演算法的思想,我的另外一篇部落格裡面有,讀者可以自己去找一下,也可以參觀其它的部落格。

#include#include#include#include#include#include#include using namespace std;

const int n = 200005;

struct edge

}e[n*2];

int n;

int head[n*2],tot,deg[n];

int cnt,vis[n],f[n*2],rk[n*2],pos[n*2],dis[n],dp[n*2][35];

//f儲存節點編號,rk儲存節點深度,pos記錄結點第一次出現的位置

mapmp;

string mm[n];

void init()

void add_edge(int s,int t,int d)

void dfs(int u,int depth)

}}void rmq(int n)

int main()

dfs(1,1);

rmq(cnt);

cin >> m;

while(m--)

getchar();

getchar();

return 0;

}

最近公共祖先 python 最近公共祖先

lca演算法樸素演算法 也就是我們所說的暴力演算法,大致的思路是從樹根開始,往下迭代,如果當前結點比兩個結點都小,那麼說明要從樹的右子樹中找 相反則從左子樹中查詢 直到找到乙個結點在當前結點的左邊,乙個在右邊,說明當前結點為最近公共祖先,如果乙個結點是另外乙個結點的祖先,那麼返回前面結點的父親結點即...

最近公共祖先 LCA 最近公共祖先

直接暴力搜尋參考 普通搜尋每次查詢都需要 樸素演算法是一層一層往上找,倍增的話直接預處理出乙個 具體做法是 維護乙個 的關係來線性求出這個陣列 int anc n 31 int dep n 記錄節點深度 void dfs int u,int parent for int i 0 i g u size...

最近公共祖先 最近公共祖先(LCA)

如題,給定一棵有根多叉樹,請求出指定兩個點直接最近的公共祖先。輸入格式 第一行包含三個正整數n m s,分別表示樹的結點個數 詢問的個數和樹根結點的序號。接下來n 1行每行包含兩個正整數x y,表示x結點和y結點之間有一條直接連線的邊 資料保證可以構成樹 接下來m行每行包含兩個正整數a b,表示詢問...