1 条题解
-
4
首先考察 怎么解决,这明显需要我们给出一个 单次求取答案的算法。
不难发现,假设对于原始串 做若干次操作,每次操作形如一些连续段的扩展与收缩,可以维护每个连续段长度以及类型,还有下一步是收缩还是扩展,当然也有可能不变,每次当一个连续段被完全删去时我们就重构一下这个连续段旁边的段的结构,将其合并,继续这个过程,复杂度是单次 的。
接下来的部分分中对正解指导意义最强的就是 的部分分,它直接明示了你不能套用上述结构,要转而思考一些支持前缀依赖转移的结构优化你单次求取答案的过程。我们可以构造以下算法,扫取前缀的同时维护一个栈:
- 当前扫到了 ,假设栈顶元素能够战胜 ,那么不进行任何操作,因为在向左扩展的时候这个段会被干掉。
- 当前扫到了 ,假设栈顶元素和 属于同类型,还是不变,因为如果栈顶在我们的过程中会被干掉,那么 同样会被干掉。
- 当前扫到了 ,假设 能够战胜栈顶元素,那么将栈顶弹出,可以证明,弹出之后的栈顶与 是一样的,不需要弹入 。
最后的栈结构从栈底到栈顶会形如 三种中的一种,此时考虑左边界收缩,那么栈底可以将后面所有元素全部干掉,下面将给出证明:
考察每一个连续段 ,假设在它前面的连续段为 ,在它后面的连续段为 ,那么本质上, 只分为这么两类:
- 所对应的 , 中有一个在胁迫它。
- 所对应的 , 中没有段在胁迫它。
那么,容易发现,在第一种情况下, 必然要完,仍然可以将其分为两类考虑:
- , 同时在胁迫 。
- , 只有一个在胁迫 。
对于第一种情况, 要完的理由是,在 完了之前, 和 必然都不会完(考察边界处的情况),那么能够支持完全干掉 。
对于第二种情况,将假设 在胁迫它,那么在 完之前, 的边界处必然会有一个位置追着 往右边走,那么结果无非只有两个:在干掉右边一些串后,遇到强敌,归纳到第一种情况,或者干掉了右边全部的串,撞墙了所以会被 蚕食到没有。容易归纳递推出对于所有满足条件的 上述两点成立, 的情况与 同理。
对于左右两边不存在段胁迫 的情况,这并不说明 不会完,需要递归到 , 随时有可能被干掉,只有到最后剩下那一个才是答案。
上述用栈模拟的过程本质上是在实现这个归纳的过程,证明过程也说明了答案与连续段长度无关。
但是区间查询是非常麻烦的,它可能需要你的结构满足一定可合并性,貌似整个栈结构的合并不太好(可能并不具有可合并性),即使实现出来,它的常数也无法通过最后一档苛刻的测试点,这里用一个巧妙的过程刻画答案:
- 记录每个时刻栈所对应的大小(对于 的每个前缀 考虑)为 ,显然 $f_i = f_{i - 1} / f_{i - 1} + 1 / \max(1, f_{i - 1} - 1)$,可以线性递推。
那么对于 全局而言,答案就是 的最后一个位置对应的元素,原因是简单的。
难道我要对于区间 都要维护一个这样的东西吗?并不需要,发现对于 的栈结构下,依次加入 内的元素得到的 ,与 单独加入得到的 ,其相对大小完全一样(对于相等的刻画略有偏差,不过不影响求解的过程),那么区间 的答案就变成了全局 在区间内最后一个最小的位置所对应的元素,查询和修改都是简单的可以用线段树实现,总时间复杂度 。
最终代码常数极小,可以以很快的速度通过最后一档测试点。
- 1
信息
- ID
- 15
- 时间
- 5000ms
- 内存
- 256MiB
- 难度
- 9
- 标签
- 递交数
- 40
- 已通过
- 3
- 上传者