[LeetCode] 1239. Maximum Length of a Concatenated String with Unique Characters 串聯字串的最大長度
笛卡爾樹學習筆記
1.笛卡爾樹介紹
笛卡爾樹是一種二叉樹,與平衡樹和堆有著密切的聯絡。其每個節點由二元組 $(k,w)$ 組成。
2.笛卡爾樹構造
笛卡爾樹的第一鍵值 $k$ 應滿足 $BST$ (中序遍歷有序)的性質。第二鍵值應滿足堆(小根堆/大根堆)的性質。
一般情況下會把陣列下標當作第一鍵值,把陣列的權值當作第二鍵值。極少數情況下會有任意給定的 $(k,w)$ 。
具體實現時,用單調棧維護從根節點開始的右鏈。我們先按照 $k$ 排序,然後一個一個插入。這樣的建樹複雜度為 $O(n)$ 。
for(int i=1;i<=n;i++){
int now=top;
while(now>0 && tr[i].w<tr[st[now]].w){
now--;
}
if(now){
tr[st[now]].ch[1]=i;
}
if(now<top){
tr[i].ch[0]=st[now+1];
}
st[++now]=i;
top=now;
}
3.笛卡爾樹性質(小根堆為例)
- 以笛卡爾樹上任意一點為根的子樹是一段連續極長區間。$w_u$ 是區間最小值,區間在保證最小值不變的情況下不能再向兩邊延伸。
- 區間 $[a,b]$ 的最小值為 $w_{lca(a,b)}$ 。
- 若 $k,w$ 都互不相同,那麼笛卡爾樹形態結構唯一。
- 對於一個有序的 $k$ 和無序的 $w$ 。笛卡爾樹上的任意一點期望高度為 $E(depth_i) = H(i)+H(n-i+1)-1$ 。其中 $H(n)=\sum\limits_{i=1}^n\dfrac{1}{i} $ (調和級數)。因此笛卡爾樹期望樹高為 $log\ n$ 。
- $Treap$ 是一種特殊的笛卡爾樹,可以運用 $Treap$ 操作維護笛卡爾樹。
- 笛卡爾樹主要解決最大/最小值問題,以及(通過記錄時間)關於插入刪除的問題
4.例題
-
[Largest Rectangle in a Histogram](
這道題可以單調棧,但是也可以笛卡爾樹。以下標為 $k$ ,矩陣高為 $w$ ,建立笛卡爾樹(小根堆)。
不難發現,笛卡爾樹上每個點的子樹大小,就是他的權值高度所能擁有的最大橫向距離(最長區間就是子樹大小)。故節點 $u$ 的最大子矩陣就是 $ Size_u \times w_u$ 。
Code:
struct Cartesian{
int k,w;
int ch[2];
};
Cartesian tr[DMAX];
int siz[DMAX];
void DFS(int now){
if(!now){
return ;
}
siz[now]=1;
DFS(tr[now].ch[0]);
siz[now]+=siz[tr[now].ch[0]];
DFS(tr[now].ch[1]);
siz[now]+=siz[tr[now].ch[1]];
}
int main(){
while(scanf("%d",&n)){
if(n==0){
break;
}
for(int i=1;i<=n;i++){
read(a[i]);
tr[i].k=i,tr[i].w=a[i];
tr[i].ch[0]=tr[i].ch[1]=0;
}
top=0;
build();
mem(siz,0);
DFS(st[1]);
ll ans=0;
for(int i=1;i<=n;i++){
ans=max(ans,1ll*siz[i]*tr[i].w);
}
printf("%lld\n", ans);
}
return 0;
}
-
[笛卡爾樹](P5854 【模板】笛卡爾樹 - 洛谷 | 電腦科學教育新生態 (luogu.com.cn))
笛卡爾樹板子題。沒有什麼奇技淫巧,直接算就行了。
-
[樹的序]([P1377 TJOI2011]樹的序 - 洛谷 | 電腦科學教育新生態 (luogu.com.cn))
還是一個板子題,但是這次要以權值為 $k$ 值,以下標為 $w$ 值。注意輸出時為先序遍歷。
Code:
struct Cartesian{ int k,w; int ch[2]; int fa; bool operator <(const Cartesian &p) const{ return k<p.k; } }; Cartesian tr[DMAX]; void DFS(int now){ if(!now){ return ; } printf("%d ", tr[now].k); DFS(tr[now].ch[0]); DFS(tr[now].ch[1]); return ; } int main(){ read(n); for(int i=1;i<=n;i++){ read(a[i]); tr[i].k=a[i],tr[i].w=i; } sort(tr+1,tr+n+1); build(); DFS(st[1]); return 0; }
4.[Periodni](SPOJ.com - Problem PERIODNI)
笛卡爾樹+樹形DP
我們以下標為 $k$ 值,高度為 $w$ 值建立笛卡爾樹。這時,我們把整個圖劃分為了很多個小矩形。
我們首先先考慮一個小情況:對於 $n*m$ 的矩形,在其中選 $k$ 個點的方案數。答案是:$\binom{n}{k}\times\binom{m}{k}\times k!$ 。考慮其組合意義加以證明,在 $n$ 行中選出 $k$ 行,在 $m$ 列中選出 $k$ 列。然後對於每一種行的選擇排列,都有 $k!$ 種列的選擇排列與其對應。
現在我們考慮DP,令 $f_{i,j}$ 表示在 $i$ 的子樹中選出 $j$ 個點的方案數。
我們先不考慮 $u$ 這個節點的矩陣。先考慮其兒子。顯而易見的邊界條件為:$f_{u,0}=1$ 。我們列舉他的兒子 $v$ ,那麼有轉移:$f_{u,j}=\sum\limits_{i=0}^jf_{v,i}\times f_{u,j-i}$ 。
那麼現在 $f_{u,i}$ 就是從 $u$ 的兒子中選擇 $i$ 個點的方案數。然後把這個點也考慮上(要減去已經通過兒子選擇過的列)。於是有:$f_{u,i}=\sum\limits_{j=1}^{i}f_{u,i-j}\times \binom{siz_u-(i-j)}{j}\times\binom{a_u-a_{fa_u}}{j}\times j!$ 。
答案就是:$f_{rt,k}$
Code:
void DFS(int now,int fa){ siz[now]=1; f[now][0]=1; for(int i=0;i<=1;i++){ if(ch[now][i]==0){ continue; } DFS(ch[now][i],a[now]); siz[now]+=siz[ch[now][i]]; int v=ch[now][i]; for(int j=min(k,siz[now]);~j;j--){ for(int l=1;l<=min(siz[v],j);l++){ f[now][j]=(0ll+f[now][j]+1ll*f[v][l]*f[now][j-l]%MOD)%MOD; } } } for(int i=min(siz[now],k);~i;i--){ for(int j=1;j<=min(i,a[now]-fa);j++){ f[now][i]=(0ll+f[now][i]+1ll*f[now][i-j]*jc[j]%MOD*C(siz[now]-(i-j),j)%MOD*C(a[now]-fa,j)%MOD)%MOD; f[now][i]=(f[now][i]%MOD+MOD)%MOD; } } }