笛卡尔树详解带建树模板及例题运用(Largest Submatrix of All 1’s,洗车 Myjnie,Removing Blocks,SPOJ PERIODNI)
文章目錄
- 笛卡爾樹(shù)
- 介紹
- 例題
- Largest Submatrix of All 1’s
- 應(yīng)用
- 「POI2015」洗車(chē) Myjnie
- [AGC028B] Removing Blocks
- SPOJ PERIODNI
笛卡爾樹(shù)
介紹
笛卡爾樹(shù)是一種數(shù)據(jù)結(jié)構(gòu),每個(gè)點(diǎn)由兩個(gè)值,鍵值key和權(quán)值val,組成
-
其鍵值滿(mǎn)足二叉樹(shù)性質(zhì)
-
即點(diǎn)的左子樹(shù)內(nèi)所有點(diǎn)的鍵值均小于點(diǎn)的鍵值,點(diǎn)的鍵值均小于點(diǎn)的右子樹(shù)內(nèi)所有點(diǎn)的鍵值
所以笛卡爾樹(shù)按照中序遍歷就會(huì)得到原序列
通常笛卡爾樹(shù)的鍵值就是下標(biāo)
-
-
其權(quán)值滿(mǎn)足堆的性質(zhì)
-
可以是大根堆,可以是小根堆
以小根堆為例,點(diǎn)的權(quán)值是該點(diǎn)子樹(shù)內(nèi)所有點(diǎn)的權(quán)值最小值
每個(gè)點(diǎn)代表一個(gè)連續(xù)區(qū)間的信息
-
如果某笛卡爾樹(shù),鍵值和權(quán)值互不相等,則建出來(lái)得笛卡爾樹(shù)是唯一的
笛卡爾樹(shù)的構(gòu)建時(shí)間復(fù)雜度是O(n)O(n)O(n)
按照鍵值是下標(biāo)的原則,順次加入點(diǎn)
則每次點(diǎn)都會(huì)走根的右邊,路徑是一條右鏈
但這只能維護(hù)笛卡爾樹(shù)的二叉樹(shù)性質(zhì),無(wú)法維護(hù)堆的性質(zhì)
實(shí)際上,使用單調(diào)棧維護(hù)笛卡爾樹(shù)的右鏈
找到第一個(gè)權(quán)值大于新點(diǎn)權(quán)值的點(diǎn)
發(fā)現(xiàn)用新點(diǎn)代替該點(diǎn),并使得該點(diǎn)及其一下所有點(diǎn)都成為新點(diǎn)的左子樹(shù)
恰好維護(hù)了笛卡爾樹(shù)的性質(zhì)
對(duì)應(yīng)的單調(diào)棧操作就會(huì)把該點(diǎn)及后面的點(diǎn)全都彈棧
然后新點(diǎn)入棧
模板
void build() {stack < int > s;for( int i = 1;i <= n;i ++ ) {while( ! s.empty() and h[s.top()] >= h[i] )lson[i] = s.top(), s.pop();if( ! s.empty() ) rson[s.top()] = i;s.push( i );}while( ! s.empty() ) rt = s.top(), s.pop(); }例題
笛卡爾樹(shù)的應(yīng)用最常見(jiàn)的就是與直方圖的結(jié)合,求最長(zhǎng)矩陣的面積
Largest Submatrix of All 1’s
POJ3494
此題無(wú)非多了一個(gè)枚舉直方圖的底邊在第幾行,所以用來(lái)當(dāng)模板題沒(méi)有問(wèn)題
題意:求全為1的矩陣最大面積
把每一列當(dāng)成一個(gè)建筑
枚舉直方圖的底邊后,計(jì)算出從第一行到底邊的每一列的高度
只取從底邊開(kāi)始延伸的連續(xù)段,半路夭折的段不需要考慮,因?yàn)榈走吺敲杜e的,那些段一定被計(jì)算過(guò)了
然后就是模板的笛卡爾樹(shù)建立
因?yàn)榈芽枠?shù)的每個(gè)點(diǎn)代表一個(gè)連續(xù)區(qū)間的最小值,所以求存在矩陣的最大值
就考慮矩陣的高度是每個(gè)點(diǎn)的可能,那可延伸的高度就是這個(gè)點(diǎn)管轄的區(qū)間長(zhǎng)度,也就等于子樹(shù)個(gè)數(shù)(含自身)
一遍dfs就可以計(jì)算得出
#include <stack> #include <cstdio> #include <iostream> using namespace std; #define maxn 2005 int n, m, rt, ans; int h[maxn], s[maxn], lson[maxn], rson[maxn]; int a[maxn][maxn];void build() {stack < int > s;for( int i = 1;i <= n;i ++ ) {while( ! s.empty() and h[s.top()] >= h[i] )lson[i] = s.top(), s.pop();if( ! s.empty() ) rson[s.top()] = i;s.push( i );}while( ! s.empty() ) rt = s.top(), s.pop(); }int dfs( int x ) {if( ! x ) return 0;int siz = dfs( lson[x] ) + dfs( rson[x] ) + 1;ans = max( ans, siz * h[x] );return siz; }int main() {while( ~ scanf( "%d %d", &m, &n ) ) {for( int i = 1;i <= m;i ++ )for( int j = 1;j <= n;j ++ )scanf( "%d", &a[i][j] );ans = 0;for( int i = 1;i <= m;i ++ ) {for( int j = 1;j <= n;j ++ ) {lson[j] = rson[j] = 0;if( a[i][j] ) h[j] ++;else h[j] = 0;}build();dfs( rt );}printf( "%d\n", ans );}return 0; }應(yīng)用
然而,笛卡爾樹(shù)很多應(yīng)用其實(shí)都非常難
且直白地考察笛卡爾樹(shù)的并不多
只能說(shuō)很多題有用到類(lèi)似笛卡爾樹(shù)的思想罷了
其實(shí)代碼通篇看不到真正的笛卡爾樹(shù)建立使用
所以說(shuō),實(shí)際上很多題就算不會(huì)笛卡爾樹(shù)的人可能也會(huì)做
硬要扯到笛卡爾樹(shù),我只能說(shuō)可能略顯牽強(qiáng)罷了
「POI2015」洗車(chē) Myjnie
dpl,r,k:[l,r]dp_{l,r,k}:[l,r]dpl,r,k?:[l,r]洗車(chē)店中最小花費(fèi)為kkk的最大收益
枚舉最小花費(fèi)的位置xxx
hx,k:[l,r]h_{x,k}:[l,r]hx,k?:[l,r]區(qū)間內(nèi)含xxx點(diǎn)的花費(fèi)上限≥k\ge k≥k的人數(shù)
dpl,r,k=max?{dpl,x?1,i≥k+dpx+1,r,j≥k+hx,k?k}dp_{l,r,k}=\max\Big\{dp_{l,x-1,i\ge k}+dp_{x+1,r,j\ge k}+h_{x,k}*k\Big\}dpl,r,k?=max{dpl,x?1,i≥k?+dpx+1,r,j≥k?+hx,k??k}
gl,r,k:max?{dpl,r,i≥k}g_{l,r,k}:\max\Big\{dp_{l,r,i\ge k}\Big\}gl,r,k?:max{dpl,r,i≥k?}
dpl,r,k=max?{gl,x?1,k+gx+1,r,k+hx,k?k}dp_{l,r,k}=\max\Big\{g_{l,x-1,k}+g_{x+1,r,k}+h_{x,k}*k\Big\}dpl,r,k?=max{gl,x?1,k?+gx+1,r,k?+hx,k??k}
把kkk(每個(gè)人的承受能力cic_ici?)離散化
時(shí)間復(fù)雜度O(n3m)O(n^3m)O(n3m)
輸出方案,就在DPDPDP轉(zhuǎn)移的時(shí)候順便記錄最大值選取的xxx即可
fi,j,kf_{i,j,k}fi,j,k? :最小值xxx的位置
lsti,j,klst_{i,j,k}lsti,j,k? :后綴ggg貢獻(xiàn)i≥xi\ge xi≥x中的收益最大值對(duì)應(yīng)的iii
本題就是完全沒(méi)有笛卡爾樹(shù)的影子,只能說(shuō)dpdpdp的設(shè)置和尋找區(qū)間最小值以及最后方案店消費(fèi)設(shè)置為cic_ici?用到了類(lèi)似笛卡爾樹(shù)的建立和詢(xún)問(wèn)
但如果沒(méi)學(xué)過(guò)笛卡爾樹(shù),也很有可能想得到這個(gè)dp以及答案構(gòu)造的貪心策略
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define maxn 52 #define maxm 4002 int n, m; struct node { int l, r, c; }p[maxm]; int c[maxm], ans[maxn]; int h[maxn][maxm]; int f[maxn][maxn][maxm], g[maxn][maxn][maxm], lst[maxn][maxn][maxm];void dfs( int l, int r, int k ) {if( l > r ) return;int x = f[l][r][k = lst[l][r][k]];ans[x] = c[k];dfs( l, x - 1, k );dfs( x + 1, r, k ); }int main() {scanf( "%d %d", &n, &m );for( int i = 1;i <= m;i ++ ) {scanf( "%d %d %d", &p[i].l, &p[i].r, &p[i].c );c[i] = p[i].c;}sort( c + 1, c + m + 1 );int cnt = unique( c + 1, c + m + 1 ) - c - 1;for( int i = 1;i <= m;i ++ )p[i].c = lower_bound( c + 1, c + cnt + 1, p[i].c ) - c;for( int len = 1;len <= n;len ++ )for( int i = 1;i <= n;i ++ ) {int j = i + len - 1;if( j > n ) break;for( int x = i;x <= j;x ++ )for( int k = 1;k <= cnt;k ++ )h[x][k] = 0;for( int k = 1;k <= m;k ++ )if( i <= p[k].l and p[k].r <= j )for( int x = p[k].l;x <= p[k].r;x ++ )h[x][p[k].c] ++;for( int x = i;x <= j;x ++ )for( int k = cnt;k;k -- )h[x][k] += h[x][k + 1];for( int k = cnt;k;k -- ) {int Max = 0; for( int x = i;x <= j;x ++ )if( Max <= g[i][x - 1][k] + g[x + 1][j][k] + c[k] * h[x][k] ) {Max = g[i][x - 1][k] + g[x + 1][j][k] + c[k] * h[x][k];f[i][j][k] = x;}if( Max >= g[i][j][k + 1] ) g[i][j][k] = Max, lst[i][j][k] = k;else g[i][j][k] = g[i][j][k + 1], lst[i][j][k] = lst[i][j][k + 1];}}dfs( 1, n, 1 );printf( "%d\n", g[1][n][1] );for( int i = 1;i <= n;i ++ ) printf( "%d ", ans[i] );return 0; }[AGC028B] Removing Blocks
AGC028B
所有n!n!n!種刪除方案的權(quán)值和
可以看成權(quán)值和的期望再乘以n!n!n!
每次刪除一個(gè)位置,然后兩邊就斷開(kāi)分別搞(相互獨(dú)立),類(lèi)似笛卡爾樹(shù)建樹(shù)過(guò)程
把刪除時(shí)間搞出來(lái),刪一個(gè)位置就是提t(yī)a當(dāng)根,然后左右接在下面
則下標(biāo)滿(mǎn)足二叉樹(shù)性質(zhì),刪除時(shí)間又滿(mǎn)足最小堆性質(zhì),這是一個(gè)標(biāo)準(zhǔn)的笛卡爾樹(shù)
恰好一個(gè)位置的貢獻(xiàn)次數(shù)就是其在笛卡爾樹(shù)上的深度(樹(shù)根深度為111)
求出點(diǎn)的期望深度,再乘以其AiA_iAi?,就是點(diǎn)的期望貢獻(xiàn),再所有點(diǎn)期望貢獻(xiàn)求和就是權(quán)值和的期望(期望現(xiàn)性)
計(jì)算點(diǎn)的深度,等價(jià)于計(jì)算有多少點(diǎn)是該點(diǎn)的祖先
考慮兩種情況
-
j<ij<ij<i
-
jjj要成為iii的祖先,就必須[j,i][j,i][j,i]區(qū)間內(nèi)jjj是最小值
不然笛卡爾樹(shù)就會(huì)抽[j,i][j,i][j,i]中最小值kkk然后分成[j,k?1][k+1,i][j,k-1][k+1,i][j,k?1][k+1,i]
-
則又變成隨機(jī)一個(gè)排列,[l,r][l,r][l,r]中最小值在lll處的概率
-
隨便選r?l+1r-l+1r?l+1個(gè)數(shù),Cnr?l+1C_{n}^{r-l+1}Cnr?l+1?
-
然后剩下的數(shù)無(wú)所謂(n?(r?l+1))!\Big(n-(r-l+1)\Big)!(n?(r?l+1))!
-
lll處強(qiáng)制是r?l+1r-l+1r?l+1個(gè)中最小值,那么剩下的r?lr-lr?l個(gè)數(shù)順序也無(wú)所謂(r?l)!(r-l)!(r?l)!
-
以上算的是所有排列的方案,最后除以n!n!n!就是概率了
-
Cnr?l+1(n?(r?l+1))!(r?l)!n!=n!(r?l+1)!(n?(r?l+1)!(n?(r?l+1))!(r?l)!n!=(r?l)!(r?l+1)!=1r?l+1\frac{C_n^{r-l+1}\Big(n-(r-l+1)\Big)!(r-l)!}{n!}=\frac{\frac{n!}{(r-l+1)!\Big(n-(r-l+1\Big)!}\Big(n-(r-l+1)\Big)!(r-l)!}{n!}=\frac{(r-l)!}{(r-l+1)!}=\frac{1}{r-l+1}n!Cnr?l+1?(n?(r?l+1))!(r?l)!?=n!(r?l+1)!(n?(r?l+1)!n!?(n?(r?l+1))!(r?l)!?=(r?l+1)!(r?l)!?=r?l+11?
-
也就是1i?j+1\frac{1}{i-j+1}i?j+11?
-
-
j>ij>ij>i 同理
- jjj要成為iii的祖先,就必須[i,j][i,j][i,j]區(qū)間內(nèi)jjj是最小值
- 則又變成隨機(jī)一個(gè)排列,[l,r][l,r][l,r]中最小值在rrr處的概率
- 隨便選r?l+1r-l+1r?l+1個(gè)數(shù),Cnr?l+1C_{n}^{r-l+1}Cnr?l+1?
- 然后剩下的數(shù)無(wú)所謂(n?(r?l+1))!\Big(n-(r-l+1)\Big)!(n?(r?l+1))!
- rrr處強(qiáng)制是r?l+1r-l+1r?l+1個(gè)中最小值,那么剩下的r?lr-lr?l個(gè)數(shù)順序也無(wú)所謂(r?l)!(r-l)!(r?l)!
- 以上算的是所有排列的方案,最后除以n!n!n!就是概率了
- Cnr?l+1(n?(r?l+1))!(r?l)!n!=n!(r?l+1)!(n?(r?l+1)!(n?(r?l+1))!(r?l)!n!=(r?l)!(r?l+1)!=1r?l+1\frac{C_n^{r-l+1}\Big(n-(r-l+1)\Big)!(r-l)!}{n!}=\frac{\frac{n!}{(r-l+1)!\Big(n-(r-l+1\Big)!}\Big(n-(r-l+1)\Big)!(r-l)!}{n!}=\frac{(r-l)!}{(r-l+1)!}=\frac{1}{r-l+1}n!Cnr?l+1?(n?(r?l+1))!(r?l)!?=n!(r?l+1)!(n?(r?l+1)!n!?(n?(r?l+1))!(r?l)!?=(r?l+1)!(r?l)!?=r?l+11?
- 也就是1j?i+1\frac{1}{j-i+1}j?i+11?
E(hi)=∑j=1i?11i?j+1+∑j=i+1n1j?i+1E(h_i)=\sum_{j=1}^{i-1}\frac{1}{i-j+1}+\sum_{j=i+1}^n\frac{1}{j-i+1}E(hi?)=∑j=1i?1?i?j+11?+∑j=i+1n?j?i+11?
令si=∑x=1i1xs_i=\sum_{x=1}^i\frac{1}{x}si?=∑x=1i?x1?
則E(hi)=si?12+sn?i+1?12=si+sn?i+1?1E(h_i)=s_i-\frac{1}{2}+s_{n-i+1}-\frac{1}{2}=s_i+s_{n-i+1}-1E(hi?)=si??21?+sn?i+1??21?=si?+sn?i+1??1
ans=n!×∑i=1n(si+sn?i+1?1)?aians=n!\times \sum_{i=1}^n(s_i+s_{n-i+1}-1)·a_ians=n!×∑i=1n?(si?+sn?i+1??1)?ai?
#include <cstdio> #define mod 1000000007 #define int long long #define maxn 100005 int n; int a[maxn], inv[maxn], s[maxn];signed main() {scanf( "%lld", &n );for( int i = 1;i <= n;i ++ )scanf( "%lld", &a[i] );inv[1] = 1;for( int i = 2;i <= n;i ++ )inv[i] = ( mod - mod / i * inv[mod % i] % mod ) % mod;for( int i = 1;i <= n;i ++ )s[i] = ( s[i - 1] + inv[i] ) % mod;int ans = 0;for( int i = 1;i <= n;i ++ )ans = ( ans + ( s[i] + s[n - i + 1] - 1 ) * a[i] % mod ) % mod;for( int i = 1;i <= n;i ++ ) ans = ans * i % mod;printf( "%lld\n", ( ans + mod ) % mod );return 0; }SPOJ PERIODNI
BZOJ2616
同樣的直方圖考慮笛卡爾樹(shù)
以高度為權(quán)值,下標(biāo)為鍵值,建立笛卡爾樹(shù)
每個(gè)點(diǎn)表示區(qū)間內(nèi)的最小值(最低高度)——實(shí)則代表了一個(gè)完整不缺的矩陣
則每個(gè)點(diǎn)的左右兒子肯定是相互獨(dú)立的(中間有至少一列的空白)
設(shè)dpi,j:idp_{i,j}:idpi,j?:i子樹(shù)內(nèi)選了jjj輛車(chē)的方案數(shù)
則先卷一下點(diǎn)左右兒子一共選了xxx輛車(chē)的方案數(shù),ti=∑j=0idplson,j?dprson,i?jt_i=\sum_{j=0}^idp_{lson,j}*dp_{rson,i-j}ti?=∑j=0i?dplson,j??dprson,i?j?
在處理當(dāng)前點(diǎn)表示的矩陣的轉(zhuǎn)移,dpnow,i=∑j=0iti?j×j!×(Hj)×(W?(i?j)j)dp_{now,i}=\sum_{j=0}^it_{i-j}\times j!\times \binom{H}{j}\times \binom{W-(i-j)}{j}dpnow,i?=∑j=0i?ti?j?×j!×(jH?)×(jW?(i?j)?)
- 枚舉nownownow點(diǎn)子樹(shù)內(nèi)安排了iii輛車(chē)
- 枚舉nownownow這一個(gè)矩陣內(nèi)安排了jjj輛車(chē),則左右兒子及其子樹(shù)總共安排了i?ji-ji?j輛車(chē)
- HHH表示nownownow點(diǎn)代表的矩陣的高度,WWW表示nownownow點(diǎn)代表的矩陣的寬度
- jjj輛車(chē)所在行不能相等,相當(dāng)于在HHH行中選jjj行的方案數(shù),列同理
- 保證了iii輛車(chē)的行不會(huì)沖突,還要解決列不會(huì)沖突
- 左右兒子及其子樹(shù)的列是與nownownow點(diǎn)矩陣的列聯(lián)通的,所以真正可以選的列要減去兒孫使用的i?ji-ji?j列
不用FFT\rm FFTFFT,直接卷就行
#include <stack> #include <cstdio> #include <cstring> using namespace std; #define mod 1000000007 #define int long long #define maxn 505 #define maxm 1000005 int n, k, rt; int h[maxn], fac[maxm], inv[maxm], lson[maxn], rson[maxn], t[maxn]; int dp[maxn][maxn];int qkpow( int x, int y ) {int ans = 1;while( y ) {if( y & 1 ) ans = ans * x % mod;x = x * x % mod;y >>= 1;}return ans; }void init( int n ) {fac[0] = inv[0] = 1;for( int i = 1;i <= n;i ++ ) fac[i] = fac[i - 1] * i % mod;inv[n] = qkpow( fac[n], mod - 2 );for( int i = n - 1;i;i -- ) inv[i] = inv[i + 1] * ( i + 1 ) % mod; }int C( int n, int m ) {if( n < m ) return 0;else return fac[n] * inv[m] % mod * inv[n - m] % mod; }void build() {stack < int > s;for( int i = 1;i <= n;i ++ ) {while( ! s.empty() and h[s.top()] > h[i] ) lson[i] = s.top(), s.pop();if( ! s.empty() ) rson[s.top()] = i;s.push( i );}while( ! s.empty() ) rt = s.top(), s.pop(); }int dfs( int now, int lst ) {int d = h[now] - lst, w = 1;if( lson[now] ) w += dfs( lson[now], h[now] );if( rson[now] ) w += dfs( rson[now], h[now] );memset( t, 0, sizeof( t ) );for( int i = 0;i <= w;i ++ )for( int j = 0;j <= i;j ++ )t[i] = ( t[i] + dp[lson[now]][j] * dp[rson[now]][i - j] ) % mod;for( int i = 0;i <= w;i ++ )for( int j = 0;j <= i;j ++ )dp[now][i] = ( dp[now][i] + t[i - j] * fac[j] % mod * C( d, j ) % mod * C( w - ( i - j ), j ) ) % mod;return w; }signed main() {scanf( "%lld %lld", &n, &k );for( int i = 1;i <= n;i ++ ) scanf( "%lld", &h[i] );init( maxm - 5 );build();dp[0][0] = 1;dfs( rt, 0 );printf( "%lld\n", dp[rt][k] );return 0; }總結(jié)
以上是生活随笔為你收集整理的笛卡尔树详解带建树模板及例题运用(Largest Submatrix of All 1’s,洗车 Myjnie,Removing Blocks,SPOJ PERIODNI)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: thinkphp5 图片压缩旋转_有非常
- 下一篇: 数据结构之fhq-treap——Chef