CF1693D
题面
定义一个序列 \(a\) 是好的,仅当可以通过删除 \(a\) 的一个单调递减子序列(可以为空)使得 \(a\) 单调递增。
给定一个 \(1\sim n\) 的排列 \(p\),你需要求出 \(p\) 有多少子段是好的。
数据范围: \(n\le 2\times 10^5\) 。
题解
方法多样。
- 自己的方法
考虑cdq分治,对于 \(mid\) 处理 \(l< mid\le r\) 的答案。
现在考虑处理大于等于 \(mid\) 的这一边。
我们可以对 \(mid\) 枚举一下是做上升的,还是下降的。
以上升为例,可以证明,在 \(mid\) 右边且作为下降的点一定是 \(a_j\) 或 \(a_{j+1}\) 其中 \(j\) 是最小的 \(j\ge mid\) 且 \(a_j>a_{j+1}\) ,那么我们就再枚举一下是哪一个,然后直接求出对应的 \(r\) 最远是多少。
这样把每种情况都求出来(同样对于 \(<mid\) 的部分),就会得到 \(4\times 4\) 种 \(l,r\) 对,然后去一下重就行了。
但是我没写qwq(所以也没法保证正确性),因为实在是太复杂了,还是考虑一下简单的做法。
- 官方题解做法
考虑如何判断一个序列有解,有一个显然的 dp:
设 \(f_{i}\) 为从起点到 \(i\) 且 \(i\) 是作为上升的 ,此时最后一个下降的值最大是多少,\(g_i\) 反过来。
然后答案就是枚举起点然后计算一次到哪个位置时不合法了。
但这是 \(O(n^2)\) 的,但题解告诉你,如果你从后往前枚举起点然后去更新,可以做到 \(O(n)\) 的复杂度!
为什么?
因为对于 \(f_i\) 来说,设 \(j\) 为小于 \(i\) 中第一个满足 \(a_{j-1}>a_j\) 的,那么 \(f_i\) 的值只可能是 \(-\inf,a_{j-1},a_j,\inf\) 这四个值。
而且修改是单调的,所以修改此时是 \(O(1)\) 的,然后我们就可以暴力往后修改而通过了。
- luogu题解区神奇做法
核心:设 \(ans_i\) 为最大的 \(ans_i\) 使得 \([i,ans_i]\) 为合法的,那么 \(ans_i\) 单调。
那么如果 \(ans_l=ans_r\) 那么对于 \(i\in[l,r],ans_i=ans_l\) 。
所以可以考虑分治,对于区间 \([l,r]\),我们先求出 \(ans_l\) 和 \(ans_r\),若 \(ans_l=ans_r\),那么直接更新,否则递归处理 \([l+1,mid]\) 和 \([mid+1,r-1]\)。
因为我们求一个 \(ans_l\) 的复杂度是 \(O(ans_l)\) 的,所以不容易被卡,甚至复杂度就是正确的。。。
反正是个新颖的乱搞做法吧。
启发
- 分析一下题目的性质就可以让一些看起来很假的做法优化成正确的做法