笛卡尔树 学习笔记

突然发现这玩意居然在提高组大纲里 /jk,赶紧突击了一下。

【模板】笛卡尔树 - 洛谷

给定一个 1n 的排列 p,构建其笛卡尔树。

即构建一棵二叉树,满足:

  1. 每个节点的编号满足二叉搜索树的性质。
  2. 节点 i 的权值为 pi,每个节点的权值满足小根堆的性质。

1n107

通过题中描述,不难发现,Treap 本质上也是一棵笛卡尔树,所以 Treap 的某些性质和笛卡尔树相通。

广义上来说,笛卡尔树是一种特殊的二叉搜索树,每个结点 i 保存着一组数对 (xi,yi)

其中,若只考虑 x,它是一棵二叉搜索树。若只考虑 y,它满足堆的性质(此处默认为小根堆)

显然,题中所给的点的编号为 xi,排列即为 yi

建树

由于 xi 递增,我们可以 O(n) 地构建笛卡尔树,过程如下:

因为 xi 要满足二叉搜索树的性质且已经递增,故顺序插入 (xi,yi) 时,显然要尽量往当前节点的右子节点走,一直走到一个满足 yu<yi 的节点 u,将 i 设为 rson(u),再将剩余子树设为 i 的左子树。

由此,我们可以维护一条根一直向右走到叶子结点的链,用一个单调栈来维护。

当栈顶 y>yi 时不断弹出(注意,由于我们要将子树接在 i 的左子树上,所以不能真的弹出去,这个过程用一个变量模拟),直到栈空或栈顶 yyi,设此时栈中有 cur 个元素:

  • cur>0,则 rson(stkcur)=i

  • cur<top,则 lson(i)=stkcur+1

由于每个结点至多入栈出栈一次,故时间复杂度为 O(n)

Code

核心代码,稍微有点压行,总共 4 行:

for(int i = 1;i <= n;++ i) {
for(cur = top;cur&&a[stk[cur]] > a[i];-- cur);
if(cur)rs[stk[cur]] = i;
if(cur < top)ls[i] = stk[cur + 1];
stk[top = cur + 1] = i;
}

例题

[TJOI2011] 树的序

给定一个 1n 的排列 p,顺序插入 p1n 形成一棵二叉搜索树,重排 p,使得 p 字典序最小的同时二叉搜索树形态不发生改变。

1n106

稍微想想就能发现,其实要求的就是 BST 的前序遍历序列。

但是 BST 的构造很烦人,随便一组 pi=i 就能把常规方法卡到 O(n2)

考虑笛卡尔树优化一下。仿照上道模板题,由于 p 满足二叉搜索树的性质,故 x 应为 p

画一下图,理解理解就能发现,把编号作为 y 插入一定是正确的。

胡的证明:因为原题中顺序插入 BST,所以每个结点的编号必然大于其父亲的编号。

然后。。就没了,套模板就行。

Code

// Problem: P1377 [TJOI2011] 树的序
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1377
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
const int maxn = 1e6 + 5;
int n,a[maxn],stk[maxn],rt,top,cur,ls[maxn],rs[maxn];
void dfs(int u) {
printf("%d ",u);
if(ls[u])dfs(ls[u]);
if(rs[u])dfs(rs[u]);
return ;
}
int main() {
scanf("%d",&n);
for(int i = 1,x;i <= n;++ i)
scanf("%d",&x),a[x] = i;
for(int i = 1;i <= n;++ i) {
for(cur = top;cur&&a[stk[cur]] > a[i];-- cur);
if(cur)rs[stk[cur]] = i;
else rt = i;
if(cur < top)ls[i] = stk[cur + 1];
stk[top = cur + 1] = i;
}
dfs(rt);// 降智了,这里直接写 stk[1] 就行了 /kel,压根不用 rt 维护根
return 0;
}

[COCI2008-2009#4] PERIODNI

题意好长,不写了

统计方案,肯定是道 DP 题。

我们把底下连通的部分称作「地基」,上面的小块称为「散块」。不断地递归拆分地基和散块,会发现这个过程就是构建笛卡尔树的过程。

(xi,yi)=(i,hi),建立笛卡尔树,在上面跑 DP 即可。

具体而言,令 f(i,j) 表示 i 的子树中选了 j 个点的方案数,前面「地基」的高度和为 h

先合并两个子节点的答案,再考虑计算,假设子节点内共选 k 个结点。

那么一定有 size(i)k 列没有被填,则方案数为 Csize(i)kjk×Ahuhjk

再转移一遍即可。至于时间复杂度,同经典树上背包,为 O(nk+max{hi})

Code

有些地方和解析里不太一致,见谅。

// Problem: P6453 [COCI2008-2009#4] PERIODNI
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P6453
// Memory Limit: 31 MB
// Time Limit: 5000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
typedef long long ll;
const int maxn = 505;
const int maxm = 1e6 + 5;
const ll mod = 1e9 + 7;
ll power(ll x,ll y) {
ll ans = 1;
for(;y;y >>= 1) {
if(y & 1)(ans *= x) %= mod;
(x *= x) %= mod;
}
return ans;
}
ll f[maxn][maxn],frac[maxm],inv[maxm];
ll C(int n,int m) {
if(n < 0||m < 0||n < m)return 0;
return frac[n] * inv[m] % mod * inv[n - m] % mod;
}
int n,m,a[maxn],stk[maxn],top,cur,son[maxn][2],sz[maxn];
void dfs(int u,int h) {
sz[u] = f[u][0] = 1;
for(int i = 0;i < 2;++ i) {
if(!son[u][i])continue ;
dfs(son[u][i] , a[u]);
sz[u] += sz[son[u][i]];
for(int j = std::min(m , sz[u]);~ j;-- j)
for(int k = 1;k <= std::min(sz[son[u][i]] , j);++ k)
(f[u][j] += f[u][j - k] * f[son[u][i]][k] % mod) %= mod;
}
for(int j = std::min(m , sz[u]);~ j;-- j)
for(int k = 1;k <= std::min(j , a[u] - h);++ k)
(f[u][j] += f[u][j - k] * frac[k] % mod * C(a[u] - h , k) % mod * C(sz[u] - j + k , k) % mod) %= mod;
return ;
}
int main() {
scanf("%d %d",&n,&m);
for(int i = 1;i <= n;++ i)
scanf("%d",&a[i]);
frac[0] = 1;
for(int i = 1;i < maxm;++ i)
frac[i] = 1ll * i * frac[i - 1] % mod;
inv[maxm - 1] = power(frac[maxm - 1] , mod - 2);
for(int i = maxm - 2;~ i;-- i)
inv[i] = 1ll * (i + 1) * inv[i + 1] % mod;
for(int i = 1;i <= n;++ i) {
for(cur = top;cur&&a[stk[cur]] > a[i];-- cur);
if(cur)son[stk[cur]][1] = i;
if(cur < top)son[i][0] = stk[cur + 1];
stk[top = cur + 1] = i;
}
dfs(stk[1] , 0);
printf("%lld\n",f[stk[1]][m]);
return 0;
}

写到这里已经晚上 23:18 了,我必须考虑一下我一笔没动的 whk 作业该怎么办了,先润了,有更多例题或者文章中的事实性错误请联系我,虽然我不会管(逃

哦对了今天疯狂星期四,有没有 fuge v50

posted @   ImALAS  阅读(85)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示