本文是 NOI 二轮复习的第一篇,总结了一些分析问题的基本套路。
问题的转化
常见的情况是:原问题不好处理,我们将其转化为其它形式的问题。
操作转化
将操作用形象的方式描述出来。
[ARC110E] Shorten ABC
如何用一种方便的方式刻画操作?考虑将 对应成 ,发现操作相当于用 替换 。
那么直接倒序 DP,转移的时候注意转移最小的前缀就可以了,因为这样的方案是转移其它前缀的超集,注意如果初始时不能启动那么答案是 。代码。
实例
我们看一些例子,来说明如何用“套路”去解决问题。
计数 1
计数题组 1。
[AGC046D] Secret Passage
看上去很容易算重,但是一次删两个,发现只需要考虑一个后缀,加入 个 , 个 能得到的字符串个数,本身就不会算重。直接转移, 后面放 , 后面放 即可。
然后计算合法的方式也是容易的,直接转移:
- 自己干掉自己,放在序列开头;
- 开头两个删掉一个,将另一个保留;
- 从已知中拿出一个来保留序列开头。
代码。
[AGC008F] Black Radius
看上去怎么都得数重?排除掉全黑的情况,钦定我们在状态相同的时候,只数那个 最小的。不难发现,这时对应的染色点是唯一的。
也即是说,设 代表 的大小为 的邻域,那么 被计入答案,当且仅当不存在与 相邻的点 使得 。要不出现这种情况,应该满足以 为根的次小深度子树深度 。
由于有的点不可以取到,因此相当于它存在一个最小合法 ,满足存在 使得 。也就是说, 的下限为所有含有 的子树的深度的最小值。代码。
数据结构
数据结构题。
[P9152] 待黑白分明
在集合中两个相邻的城市 (假定 )应该满足 。
式子中后面这个东西抽搐的样子不难想到将其扔到大根笛卡尔树上。
考虑 在树上的位置,它们一定存在祖先关系,否则它们的 LCA 就把限制干烂了。如果说 是 的祖先,那么 应该在 左儿子的右儿子链上,否则这条链上的点会把限制干烂。当然这里钦定了 ,否则在右儿子的左儿子链上也是可以的。
考虑到 的选取条件是按照高度排序,这里钦定从大到小排序,相邻的节点必然满足 是 的祖先,且 在 的左儿子的右儿子链或者右儿子的左儿子链上。
如何处理单次询问?考虑树形 DP,令 代表以 开头的合法子集数量, 代表 左儿子链/右儿子链的 值的和,直接计算 即可,初始时只给在询问区间内的 初始化,然后树形 DP 一次即可。
扫描线维护值域维,从大到小依次加入新的数并重新计算 的值,然后较大数不会影响小数的答案,因此直接树状数组上查询即可。
若数据随机,那么每个数在笛卡尔树上的期望深度是 的,因此直接暴力维护就是对的。
使用树上随机撒点分块,预处理出每个关键点 后对每个点 对于每个点 造成的影响并统计影响的树上前缀和,然后每次修改先暴力改到关键点,这一部分是 的,然后跳 次,打一个标记,并同时统计 的前缀和。查询的时候枚举所有关键点,将所有 加起来即可。
时间复杂度 ,空间复杂度 。逐块处理可以做到 的空间,这是最常规的思路。
也有其它做法,这里写一种。
首先我们要求出序列中每个值的左边和右边分别第一个大于它的值 。
按照高度的值域进行分块。提前预处理出从某个点出发,到达和当前点同块的所有方案数 ,以及某个点到达它左边和右边两个方向第一个和当前值不属于一个值域块的方案数 ,以及它们所对应的位置 。可以 完成。
修改时将预处理好的值扔到下一个块里,更新 代表第 个值域块所影响的值域前缀()的答案。其只会跳最多不超过两倍块数次。同时可以处理出比 小的值跳到 的方案数 。
每次查询,因为小的值每加入,所以直接从小到大加上区间包含的所有整块的方案 。然后就是唯一的那个散块,直接暴力 DP 即可,时间复杂度 ,空间复杂度 。代码。
综合应用
挑战自我吧!
[NOI2023] 桂花树
首先考虑 ,发现答案是 。
对于 ,如果 ,那么发现就是再乘上 ,根据此容易推测出 的规律。
考虑 有什么用,样例解释给了我们答案,可以直接分裂出一个空白节点,然后再填上一个节点。
由于 很小,对需要填写的空白节点状压,直接转移即可。代码。