最近公共祖先 倍增演算法

2021-08-29 23:42:01 字數 2794 閱讀 9452

模板題是這個樣子的:給你一顆有根樹,每次查詢兩個節點的最近公共祖先。

最近公共祖先為何物?簡單來說,就是兩點間的路徑中深度最小的那個節點。

那麼,有什麼辦法可以求最近公共祖先呢?

最樸素的辦法:首先讓兩個節點中深度大的節點往上乙個乙個節點地跳,直到兩個節點深度一樣,然後一起跳,直到他們跳到同乙個點。

顯然,這種o(n)的做法普遍不受到歡迎,於是,我們就需要尋找更優的跳法。

……那一格一格跳很慢,不如兩格兩格跳?

既然可以兩格兩格跳,不如……三格三格跳?

但我們不可能記錄每一次可以跳到哪個位置,因為空間複雜度是n2的,血虧,那怎麼辦呢?

顯然,我們無能為力……

那就丟掉一些吧!

於是我們只留下其中一些,並且要保證留下的步數一定可以組合成任何步數,並且盡可能讓加數少一些,多了就需要列舉很多次了。

比方1——關於組合步數

假如我留下的步數是2和3,那麼我們只能組合出

2 、3

、4、5

、6、7

、8、9

、10、11

……

2、3、4、5、6、7、8、9、10、11……

2、3、4、

5、6、

7、8、

9、10

、11…

…很顯然,不能組合出1步,所以我們做不到前進1步,那麼顯然不能這麼做。

比方2——關於如何盡可能讓加數少一些

看完上面,相信大家肯定都能想到解決辦法——留下步數1,那我們如何使得剩下的步數可以快速組合出所有步數呢?這點很重要,決定了演算法的效率。比如,假如你留下的是步數1

11和100

10010

0,那假如你需要前進99

9999

步怎麼辦?還是只能一步一步往前走(因為不存在往回走這種操作,所以不能走一百步之後再走回來一步,因為我們往祖先那裡跳的時候,並不需要知道自己從**來,只用關心要去**),那麼這種做法又退化成o(n

)o(n)

o(n)

的暴力了。

聯想一下二進位制,因為我們可以將任何步數轉化成乙個二進位制數,比如10

1010

,二進位制是1010

1010

1010

,發現10

1010

由23+21得到,推廣一下,可以發現每一種步數都可以由若干個2

22的某些次方加起來得到,所以,我們只需要留下20,21,22……即可

問題來了!

為啥不留下30,31,32……?

舉個栗子,假如步數為5,那麼就需要走31+2x30步,需要走三次,而如果像上面的方法,就只需要走兩次。

那40,41,42……呢?

再舉個栗子,假如要走7步,就需要走41+3x40步,也就是走4次,而之前的辦法只需要走3次,虧吧。

總結一下上面的問題,仔細觀察一下,發現其他的走法問題就在於,1步走的太多次,拖累總體效率。雖然對於一些特殊的步數可能走的次數就很少,但考慮對於大多步數,原來的做法還是要優一些。

所以原來的辦法還是最好的!

至此,我們已經找到了最優的跳法,那麼,怎麼求21次方步之後跳到**呢?

跳到**呢?

到**呢?

**呢?

裡呢?呢?

?誒?我父親的父親不就是我跳21步之後可以跳到的地方嗎!

我父親的父親不就是我走兩次20步可以走到的地方嗎?!

那不就其他的不也就很容易了嘛

推廣一下上面的東西,可以知道對於i點,它跳2n步其實就是跳兩次2n-1可以到達的地方。

於是就做完了。

上**!

#include

#include

int n,m,k,len=0;

struct node

;node e[

1000010];

int f[

500010][

20];//f[i][j]表示i節點往上跳2^j步可以跳到的節點

int first[

500010];

//鄰接表要用的陣列

int h[

500010];

//記錄節點深度

void

buildroad

(int x,

int y)

//建邊

void

dfs(

int x,

int fa)

//當前節點和當前節點的父親節點

}int

main()

h[0]

=0;h[k]=1

;//設k為根節點

dfs(k,0)

;//其實這個dfs就是為了暴力找出f[i][0]和搞出h陣列啦

for(

int j=

1;j<=

18;j++

)for

(int i=

1;i<=n;i++

)//記住這兩個迴圈不能反過來寫,具體為什麼大家可以自己想一想啦(其實就是我自己懶得寫。。)

f[i]

[j]=f[f[i]

[j-1]]

[j-1];

while

(m--

)int up=18;

while

(h[x]

)//假如還比x要深

up=18;

while

(x!=y)

//一起跳

printf

("%d\n"

,y);

//這裡輸出x也沒問題

}}

LCA 最近公共祖先 (倍增演算法)

首先了解一下我們 最近公共祖先 e和g的lca為a l和j的lca為d k和f的lca為b 然後 倍增 用到了二進位制和 dp 的思想 倍增 就是 1 2 4 8 16 任何乙個數 都是可以右 這些數相加得到的。了解一下二進位制 首先 定義 fa i j 為 從 i 節點 向上走 2 j 個節點,d...

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

樹上倍增求lca lca指的是最近公共祖先 least common ancestors 如下圖所示 4和5的lca就是2 那怎麼求呢?最粗暴的方法就是先dfs一次,處理出每個點的深度 然後把深度更深的那乙個點 4 乙個點地乙個點地往上跳,直到到某個點 3 和另外那個點 5 的深度一樣 然後兩個點一...

節點的最近公共祖先 倍增演算法

題目描述 這是乙個裸的lca問題,即求書上兩個節點的最近公共祖先。我們可以用樹上倍增來做 當然,在做之前我們假設不知道該演算法。那麼我們如何來做這種型別的題目呢?顯然,我們可以用暴力來做,找到兩點的最近公共祖先,我們可以用前向星存雙向邊,然後依次儲存每個點到的根的路徑。然後找到最先同時出現在兩條路徑...