1. 程式人生 > 實用技巧 >笛卡爾樹

笛卡爾樹

閒的沒事翻新題,突然想起笛卡爾樹還沒學,於是寫了寫笛卡爾樹的模板題。

題意

  • 給一個排列p1~pn,i號點權值為pi,要求建一棵以編號為關鍵字的二叉搜尋樹(中序序列為1~n),且以權值為關鍵字的小根堆。

  • n <= 1e7

思路分析

難度在於O(n)建樹。但既然編號是連續的,那麼我們就每次使勁往右插即可。因此我們要維護一條從根節點一直向右的鏈

又因為要維護小根堆性質,每次在鏈上找到一對相鄰點,使得父親權值大於它,且兒子權值小於它。把兒子設為自己的左兒子,並把自己設為父親的右兒子即可。

Code:

for (register int i = 1; i <= n; ++i) {
	while (top && val[sta[top]] > val[i])	ls[i] = sta[top--];
	if (top)	rs[sta[top]] = i;
	sta[++top] = i;
}

然而並不知道它有什麼用。

一個不那麼顯然的性質

其實還是挺顯然的

笛卡爾樹上每個節點的子樹中的編號集合一定是一段連續的區間,不過一段連續的區間卻不一定是一個連通塊。

實用版建樹:

維護每個點的子樹編號區間(這個點一定是區間中的權值的最值點),找出區間中該點左右的最值點,該點向那兩個點連邊,然後遞迴子問題。

例題

SP3734 PERIODNI - Periodni

(大概是最經典的笛卡爾樹題了)

給個直方圖,問放 \(k\) 個不在同一行同一列的點的方案數。

我們發現直方圖其實還可以橫著劃分,劃分成一個個方塊(見 lhm_大佬的題解),然後會出現樹形結構(類似樹形依賴關係),用類似樹形揹包的組合計數DP解決即可。

這個樹形結構其實就是笛卡爾樹。

可見,笛卡爾樹擅長把序列最值“瓶頸”問題轉化為樹上的問題。

P3246 [HNOI2016]序列

這題似乎是想用笛卡爾樹優化rmq的一個log,但似乎那個題解被 hack 了。

題解在這

強制線上1e7詢問的加強版(在鴿)

P5044 [IOI2018] meetings 會議

看不懂題解+看不懂題解程式碼+不會線段樹,可能還要鴿一段時間。

lsr大佬的題解

不知道為啥我剛要做,lsr大佬就寫好題解了,然後我還看不懂qaq

2020.8.28 Update:

寫完了。感謝 ywy 學長的講解!

笛卡爾樹上 DP,用線段樹來優化。不過用到笛卡爾樹的一個套路:按照最大值把詢問區間拆成兩半,這樣的話每一半都會有一側是完整的,利用這個性質方便解題。

jzp 的題解