笛卡尔树
较为抽象对新手极其不友好
定义
笛卡尔树是二叉搜索树的一种,每一个节点都有一个键值对 \((k,w)\),其中 \(k\) 满足BST性质,\(w\) 满足堆性质。
这不就是Treap吗?
事实上Treap是笛卡尔树的一种,只不过Treap的 \(w\) 完全随机,Treap是一种动态笛卡尔树。
来自oi-wiki(图源自维基百科)
建树
建树时,要保证 \(k\) 是有序的,依次将 \((k,w)\) 插入树中。
同时,由于 \(k\) 是升序插入,所以 \(k\) 只会成为有最靠右的叶节点,这个节点没有子节点。
我们定义「右链」为从根节点开始,一直向右走的一条链,那么插入的节点一定是在右链上。
我们用单调栈维护这个「右链」,当加入一条新节点时,不断将 \(w\) 小与新节点 \(w\) 的节点出栈,后将这个新节点作为栈顶的右子节点,将出栈的那一条链作为这个新节点的左子节点。
来自oi-wiki
由于每个节点做多进栈一次,出栈一次,故时间复杂度为 \(O(n)\)。
代码
for(int i=1,pos=0,top=0;i<=n;++i){
pos=top;
while(pos&&w[stk[pos]]>w[i]) pos--;
if(pos) rs[stk[pos]]=i;
if(pos<top) ls[i]=stk[pos+1];
stk[top=++pos]=i;
}
需要好好理解。
例题
【模板】笛卡尔树 \(^{luogu P5854}\)
一道模版题,节点编号为 \(k\) ,权值为 \(w\),建树、计算、输出即可。
一定要用快读!。
代码
#include<bits/stdc++.h>
using namespace std;
const int NUM=1e7+10;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch-'0');
ch=getchar();
}
return x*f;
}
int n,w[NUM],stk[NUM],ls[NUM],rs[NUM];
long long l,r;
int main(){
n=read();
for(int i=1,pos=0,top=0;i<=n;++i){
w[i]=read();
pos=top;
while(pos&&w[stk[pos]]>w[i]) pos--;
if(pos) rs[stk[pos]]=i;
if(pos<top) ls[i]=stk[pos+1];
stk[top=++pos]=i;
}
for(int i=1;i<=n;++i){
l^=1ll*i*(ls[i]+1),r^=1ll*i*(rs[i]+1);
}
cout<<l<<' '<<r;
return 0;
}
[TJOI2011] 树的序\(^{luoguP1377}\)
与上一道正好相反,将值作为 \(k\),下标作为 \(w\),建树,前序遍历输出即可。
代码
#include<bits/stdc++.h>
using namespace std;
const int NUM=1e6+10;
inline void write(int x){
if(x<0)x*=-1,putchar('-');
if(x>9)write(x/10);
putchar(x%10+'0');
return;
}
int n;
int ls[NUM],rs[NUM],stk[NUM],a[NUM];
void dfs(int x){
if(x) write(x),putchar(' '),dfs(ls[x]),dfs(rs[x]);
}
int main(){
cin>>n;
for(int i=1,x;i<=n;++i){
cin>>x;
a[x]=i;
}
for(int i=1,pos=0,top=0;i<=n;++i){
pos=top;
while(pos&&a[stk[pos]]>a[i]) pos--;
if(pos) rs[stk[pos]]=i;
if(pos<top) ls[i]=stk[pos+1];
stk[top=++pos]=i;
}
dfs(stk[1]);
return 0;
}
Takahashi is Slime\(^{AT\_arc189\_d}\)
以下标为 \(k\) 建立大根笛卡尔树,则相邻的节点(子父节点)是相邻的,且在根这里的史莱姆可以吃掉子树里的所有史莱姆,设 \(sum_i\) 为以 \(i\) 为根的子树所有权值之和,若其父节点的大小小于当前节点的 \(sum\) 就可以将其父节点吃掉,以此类推就可以知道所有史莱姆的大小。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int NUM=1e6+10;
int n,stk[NUM],w[NUM],ls[NUM],rs[NUM],sum[NUM];
int dp[NUM];
void dfs(int x){
if(!x) return;
sum[x]=w[x];
dfs(ls[x]),dfs(rs[x]);
sum[x]+=sum[ls[x]]+sum[rs[x]];
}
void dfs2(int x,int f){
if(!x) return;
if(f&&sum[x]>w[f]) dp[x]=dp[f];
else dp[x]=sum[x];
dfs2(ls[x],x),dfs2(rs[x],x);
}
signed main(){
cin>>n;
for(int i=1,pos=0,top=0;i<=n;++i){
cin>>w[i];
pos=top;
while(pos&&w[stk[pos]]<w[i]) pos--;
if(pos) rs[stk[pos]]=i;
if(pos<top) ls[i]=stk[pos+1];
stk[top=++pos]=i;
}
dfs(stk[1]);
dfs2(stk[1],0);
for(int i=1;i<=n;++i){
if(w[rs[i]]<w[i]||ls[i]||ls[rs[i]]) cout<<dp[i]<<' ';
else cout<<w[i]<<' ';
}
return 0;
}
[COCI 2008/2009 #4] PERIODNI\(^{luoguP6453}\)
一道笛卡尔树的DP题。
参考_luanyi_的题解
先考虑如果图形是个矩形该怎么办?
\(n、m\)是长和宽。
横着在 \(n\) 个里面选 \(k\) 个,竖着在 \(m\) 个里面选 \(k\) 个,最后的 \(k!\) 是 \(A_k^k\)。很好理解吧……
于是我们就可以将这个诡异的图形拆成若干个矩形,由于不相连的部分互不干扰,所以采用从下向上分的策略。

如果我们知道高度最小的位置 \(x\),那么就可以递归到 \([1,x-1]\) 和 \([x+1,n]\)。
于是我们就可以使用笛卡尔树,让下标满足BST性质,高度满足堆性质,根节点就是 \(x\),其左子树就是 \([1,x-1]\),右子树就是 \([x+1,n]\)。
设 \(f[x][k]\) 表示到达 \(x\) 节点所对应的矩形及其上方的矩形放入 \(k\) 个数的方案数。
当到达叶节点(即上方没有矩阵)时:
其中 \(n、m\) 时矩形的长和宽。
下面考虑合并
设左上矩阵编号为 \(ls[x]\),右上矩阵编号为 \(rs[x]\)。
其中 \(n、m\) 为 \(x\) 对应的矩形的长和宽。
可以理解为左边放 \(i\) 个,右边放 \(j\) 个,下面就要放 \(k-i-j\) 个。
但这个会T。
考虑优化。
设:
则原转移方程可以写为:
于是这道题就解决了。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int NUM=510;
const int mod=1e9+7;
int ksm(int a,int b){
int ans=1;
for(;b;b>>=1,a=a*a%mod)
if(b&1) ans=ans*a%mod;
return ans;
}
int n,jc[(int)1e6+10]={1},njc[(int)1e6+10]={1};
int C(int n,int m){
if(m>n) return 0;
return jc[n]*njc[n-m]%mod*njc[m]%mod;
}
int cal(int n,int m,int k){
return C(n,k)*C(m,k)%mod*jc[k]%mod;
}
int stk[NUM],h[NUM],ls[NUM],rs[NUM],siz[NUM];
void dfs0(int x){
if(!x) return;
dfs0(ls[x]),dfs0(rs[x]);
siz[x]=siz[ls[x]]+siz[rs[x]]+1;
}
int k,f[NUM][NUM],g[NUM];
void dfs(int x,int low){
int hu=h[x]-low;
if(!ls[x]&&!rs[x]){
f[x][0]=1;
for(int i=1;i<=k;++i) f[x][i]=cal(siz[x],hu,i);
return;
}
if(!ls[x]||!rs[x]){
int y;
if(!ls[x]) y=rs[x];
else y=ls[x];
dfs(y,h[x]);
memset(g,0,sizeof(g));
f[x][0]=g[0]=1;
for(int i=1;i<=k;++i)
for(int j=0;j<=i;++j)
g[i]=(g[i]+f[0][j]*f[y][i-j])%mod;
for(int i=1;i<=k;++i)
for(int j=0;j<=i;++j)
f[x][i]=(f[x][i]+g[j]*cal(hu,siz[x]-j,i-j))%mod;
return;
}
dfs(ls[x],h[x]);
dfs(rs[x],h[x]);
memset(g,0,sizeof(g));
f[x][0]=g[0]=1;
for(int i=1;i<=k;++i)
for(int j=0;j<=i;++j)
g[i]=(g[i]+f[ls[x]][j]*f[rs[x]][i-j])%mod;
for(int i=1;i<=k;++i)
for(int j=0;j<=i;++j)
f[x][i]=(f[x][i]+g[j]*cal(hu,siz[x]-j,i-j))%mod;
}
signed main(){
cin>>n>>k;
for(int i=1;i<=1e6;++i) jc[i]=jc[i-1]*i%mod,njc[i]=ksm(jc[i],mod-2);
for(int i=1,top=0,pos=0;i<=n;++i){
cin>>h[i];
pos=top;
while(pos&&h[stk[pos]]>h[i]) pos--;
if(pos) rs[stk[pos]]=i;
if(pos<top) ls[i]=stk[pos+1];
stk[top=++pos]=i;
}
dfs0(stk[1]);
f[0][0]=1;
dfs(stk[1],0);
cout<<f[stk[1]][k]%mod;
return 0;
}



浙公网安备 33010602011771号