笛卡尔树 学习笔记
突然发现这玩意居然在提高组大纲里 /jk,赶紧突击了一下。
给定一个 的排列 ,构建其笛卡尔树。
即构建一棵二叉树,满足:
- 每个节点的编号满足二叉搜索树的性质。
- 节点 的权值为 ,每个节点的权值满足小根堆的性质。
。
通过题中描述,不难发现,Treap 本质上也是一棵笛卡尔树,所以 Treap 的某些性质和笛卡尔树相通。
广义上来说,笛卡尔树是一种特殊的二叉搜索树,每个结点 保存着一组数对 。
其中,若只考虑 ,它是一棵二叉搜索树。若只考虑 ,它满足堆的性质(此处默认为小根堆)
显然,题中所给的点的编号为 ,排列即为 。
建树
由于 递增,我们可以 地构建笛卡尔树,过程如下:
因为 要满足二叉搜索树的性质且已经递增,故顺序插入 时,显然要尽量往当前节点的右子节点走,一直走到一个满足 的节点 ,将 设为 ,再将剩余子树设为 的左子树。
由此,我们可以维护一条根一直向右走到叶子结点的链,用一个单调栈来维护。
当栈顶 时不断弹出(注意,由于我们要将子树接在 的左子树上,所以不能真的弹出去,这个过程用一个变量模拟),直到栈空或栈顶 ,设此时栈中有 个元素:
-
若 ,则
-
若 ,则
由于每个结点至多入栈出栈一次,故时间复杂度为 。
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] 树的序
给定一个 的排列 ,顺序插入 形成一棵二叉搜索树,重排 ,使得 字典序最小的同时二叉搜索树形态不发生改变。
稍微想想就能发现,其实要求的就是 BST 的前序遍历序列。
但是 BST 的构造很烦人,随便一组 就能把常规方法卡到 。
考虑笛卡尔树优化一下。仿照上道模板题,由于 满足二叉搜索树的性质,故 应为 。
画一下图,理解理解就能发现,把编号作为 插入一定是正确的。
胡的证明:因为原题中顺序插入 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 题。
我们把底下连通的部分称作「地基」,上面的小块称为「散块」。不断地递归拆分地基和散块,会发现这个过程就是构建笛卡尔树的过程。
令 ,建立笛卡尔树,在上面跑 DP 即可。
具体而言,令 表示 的子树中选了 个点的方案数,前面「地基」的高度和为 。
先合并两个子节点的答案,再考虑计算,假设子节点内共选 个结点。
那么一定有 列没有被填,则方案数为
再转移一遍即可。至于时间复杂度,同经典树上背包,为 。
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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】