笛卡爾樹

dingzibo_qwq發表於2024-04-06

1 定義

笛卡爾樹是一種二叉樹,每一個節點由二元組 \((k,w)\) 組成。要求 \(k\) 滿足二叉搜尋樹的性質,\(w\) 滿足堆的性質。

\(k,w\) 都確定,且 \(k,w\) 互不相同時,笛卡爾樹的結構是唯一的,如圖:

看到這個定義,會發現與 Treap 十分相似。

實際上,Treap 就是一種特殊的笛卡爾樹。

通常情況下,將下標作為 \(k\)(如上圖)。

2 建樹

2.1 過程

首先由定義可以直接得出一個 \(O(n\log n)\) 演算法,但是重點不在於此。

笛卡爾樹有線性的構造方式。

我們先將所有元素按照 \(k\) 排序,那麼這樣我們按順序插入的時候,一定是插入到樹的右鏈(即從根節點不斷走向右兒子所形成的鏈)。

那麼此時我們觀察右鏈,假設當前節點是 \(p\),那麼現在要滿足的就是堆的性質。我們從右鏈末端開始比較,當出現第一個 \(x\) 滿足 \(x_w<p_w\) 時,就將 \(p\) 接到 \(x\) 右兒子上,而 \(x\) 原先的右兒子變為 \(p\) 的左兒子。

如果沒有找到這個 \(x\),那麼 \(p\) 就成為了根節點。

現在我們考慮維護右鏈,因為維護右鏈的過程中就可以建好樹。顯然右鏈的 \(w\) 是要滿足單調遞增的。那麼插入元素的過程中維護一個單調遞增的結構,這顯然是單調棧。

2.2 程式碼

有了上面的分析,程式碼就不難了:

int s[Maxn], top;
for(int i = 1; i <= n; i++) {
	int k = top;
	while(k && w[s[k]] > w[i]) k--;
	if(k) rs[s[k]] = i;
	if(k < top) ls[i] = s[k + 1];
	s[++k] = i;
	top = k;
}

3 用途

笛卡爾樹適合解決與最值相關的問題,不過先給出一些性質(以小根堆為例):

  1. \(u\) 為根的子樹是一段連續的區間,且區間最小值就是 \(u_w\)
  2. 區間上 \([l,r]\) 的最小值就是 \(\text{Lca}(l,r)_w\)
  3. \(y\) 隨機,則樹高期望為 \(\log\)。(這樣做就是 Treap 了)。

下面舉一例:

3.1 [例 1] 最大子矩形問題

形式化題意:給定陣列 \(a\),求 \(\max\{\min\limits_{i=l}^r(a_i)\times (r-l+1)\},1\le l\le r\le n\)

這道題是經典的單調棧題,不過也可以用笛卡爾樹。

具體的,我們以下表為 \(k\),值為 \(w\) 建立笛卡爾樹。接下來我們列舉最小值,然後找最小值為它的最長區間。

由上面的性質 \(1\)​ 得,最長區間就是子樹大小,於是兩者相乘取最大值即可。

相關文章