闲的没事翻新题,突然想起笛卡尔树还没学,于是写了写笛卡尔树的模板题。
题意
-
给一个排列p1pn,i号点权值为pi,要求建一棵以编号为关键字的二叉搜索树(中序序列为1n),且以权值为关键字的小根堆。
-
n <= 1e7
思路分析
难度在于O(n)建树。但既然编号是连续的,那么我们就每次使劲往右插即可。因此我们要维护一条从根节点一直向右的链。
又因为要维护小根堆性质,每次在链上找到一对相邻点,使得父亲权值大于它,且儿子权值小于它。把儿子设为自己的左儿子,并把自己设为父亲的右儿子即可。
Code:
for (register int i = 1; i <= n; ++i) {
while (top && val[sta[top]] > val[i]) ls[i] = sta[top--];
if (top) rs[sta[top]] = i;
sta[++top] = i;
}
然而并不知道它有什么用。
一个不那么显然的性质
其实还是挺显然的
笛卡尔树上每个节点的子树中的编号集合一定是一段连续的区间,不过一段连续的区间却不一定是一个连通块。
实用版建树:
维护每个点的子树编号区间(这个点一定是区间中的权值的最值点),找出区间中该点左右的最值点,该点向那两个点连边,然后递归子问题。
例题
SP3734 PERIODNI - Periodni
(大概是最经典的笛卡尔树题了)
给个直方图,问放 (k) 个不在同一行同一列的点的方案数。
我们发现直方图其实还可以横着划分,划分成一个个方块(见 lhm_大佬的题解),然后会出现树形结构(类似树形依赖关系),用类似树形背包的组合计数DP解决即可。
这个树形结构其实就是笛卡尔树。
可见,笛卡尔树擅长把序列最值“瓶颈”问题转化为树上的问题。
P3246 [HNOI2016]序列
这题似乎是想用笛卡尔树优化rmq的一个log,但似乎那个题解被 hack 了。
强制在线1e7询问的加强版(在鸽)
P5044 [IOI2018] meetings 会议
看不懂题解+看不懂题解代码+不会线段树,可能还要鸽一段时间。
不知道为啥我刚要做,lsr大佬就写好题解了,然后我还看不懂qaq
2020.8.28 Update:
写完了。感谢 ywy 学长的讲解!
笛卡尔树上 DP,用线段树来优化。不过用到笛卡尔树的一个套路:按照最大值把询问区间拆成两半,这样的话每一半都会有一侧是完整的,利用这个性质方便解题。