模板題是這個樣子的:給你一顆有根樹,每次查詢兩個節點的最近公共祖先。
最近公共祖先為何物?簡單來說,就是兩點間的路徑中深度最小的那個節點。
那麼,有什麼辦法可以求最近公共祖先呢?
最樸素的辦法:首先讓兩個節點中深度大的節點往上乙個乙個節點地跳,直到兩個節點深度一樣,然後一起跳,直到他們跳到同乙個點。
顯然,這種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問題,即求書上兩個節點的最近公共祖先。我們可以用樹上倍增來做 當然,在做之前我們假設不知道該演算法。那麼我們如何來做這種型別的題目呢?顯然,我們可以用暴力來做,找到兩點的最近公共祖先,我們可以用前向星存雙向邊,然後依次儲存每個點到的根的路徑。然後找到最先同時出現在兩條路徑...