1. 程式人生 > >Tarjan求LCA

Tarjan求LCA

### 前言: 沒想到吧,$tarjan$不僅可以用來求割點和橋,縮點,還能求$LCA$。不過,**$tarjan$求$LCA$是離線的**,要線上演算法的話還是學倍增吧。 ------------ ### 正題: 這次的$tarjan$不需要回溯值和$dfs$序,本質的來說,其實$tarjan$求$LCA$跟割點和橋,縮點沒有任何關係~~一個人發明的算不算~~。 **前置知識:[並查集](https://www.cnblogs.com/bzzs/p/13095399.html)** 當然,$tarjan$的其他兩個演算法跟$dfs$有關,求$LCA$也不例外,我們是在$dfs$的基礎上,一步一步求出來的。 步驟如下: 1. 對這課樹進行$dfs$,從根開始 2. 對於每個節點,我們不先標記這個點走過,回溯的時候才標記 3. 對於每個節點,遍歷與之相鄰且未走過的點,並把這些點的父親標記為當前節點,相當於合併這些點 4. 對於每個節點,當與之相鄰的點遍歷完後,查詢在求LCA問題中與自己相關的問題,看它問題中的另外一個點有沒有被查詢到,有的話就把這兩個點的答案賦值為另外一個點的父親(當然是合併後的父親) 第三步的是否走過時指遍歷到了沒有,不是標記。對於合併,合併後查詢父親,我們就用並查集來完成。 這樣自然是不好理解的,來看個例子(以下的$find$函式就是普通並查集的$find$): ![](https://s1.ax1x.com/2020/07/31/aleJPI.png) 這是我們的圖,現在假設我們要求$3$和$4$,$2$和$6$的$LCA$。 先進行第一步,此時我們先是從$1$開始搜,先到的地方是$3$,然後看與$3$相關的節點$4$,$4$沒有被搜到,我們就退出,並標記$3$,把他的父親標記為$2$,合併掉$3$。 ![](https://s1.ax1x.com/2020/07/31/ale3ad.png) 此時,$2$的兒子沒遍歷完開始遍歷$4$,與$4$相關的節點$3$,是搜過的,此時$LCA$$3$,$4$就是$find$($3$)也就是$2$(注意不能$find$($4$),而是$find$與之相關的另外一個節點)。然後合併$4$,把$4$的父親標記為$2$。 ![](https://s1.ax1x.com/2020/07/31/aleYGt.md.png) 這時應該遍歷$2$了,發現與之相關的$6$未找到,於是把$2$的父親標記為$1$,自然,此時$3$,$4$的父親也為$1$了。 ![](https://s1.ax1x.com/2020/07/31/ale8IA.md.png) 後面的就以此類推了。這一步應該判斷$6$,求出$LCA2$,$6$為$1$,合併$6$,標記父親。 ![](https://s1.ax1x.com/2020/07/31/ale1VH.md.png) 把$5$合併,父親為$1$。 ![](https://s1.ax1x.com/2020/07/31/aletRP.md.png) 到$1$了後就沒有了,演算法完結。 接下來講講實現。 ------------ ### 例題: [洛谷 P3379 【模板】最近公共祖先(LCA)](https://www.luogu.com.cn/problem/P3379) 這就是模板了吧,我把程式碼貼一貼,理解一下(特別短!!!而且這道題對於倍增和$RMQ$都需要卡卡常,而$tarjan$我用$vector$建圖不加快讀快寫就能過,當然得把註釋刪掉,不然會$T$)。 程式碼: ```cpp #