4.15——edu151D
edu151D
限时每日一题day30。调了 \(2h\) 多才过。写得非常难绷,用了一大堆 \(ds\) 维护,最后发现题解代码巨短。。。
赛时思路:
先求得 \(a\) 的前缀和数组 \(pre\)。
首先缩小答案范围:可以发现 \(k >= 0\) 显然成立,并且 \(pre\) 中的某个元素一定满足要求。证明:
若 \(k\) 不等于 \(pre\) 中的任何一个元素,则当 \(k > max(pre)\) 时,\(k = max(pre)\) 可以达到同样的效果;否则,\(k\) 总是可以取第一个比它大的 \(pre\)。这样在不会改变 \(Rating\) 第一次发生转折位置的同时,也会使得 \(Rating\) 在转折刚开始发生后更大,这样答案一定不会更劣。
因此可以 \(O(n)\) 枚举 \(k\),取最终结果最大的那个 \(k\) 即可。
那么,问题就转化为了:对于某个给定的 \(k\),能快速计算出经过序列 \(a\) 后的结果。
写了个巨麻烦的 \(O(logn)\) 写法:
想法就是,计算出第一个突转 \(<k\) 的位置。此时值变为 \(k\),下限也是 \(k\)。这个变化与初值为 \(0\),下限为 \(0\) 等价。可以通过对每个后缀预处理得到(记考虑从 \(a[j]\) 开始往右走,初值为 \(0\),下限也为 \(0\),最终结果为 \(res[j]\))。
而计算这个位置,需要两步:
- 找到第一个 \(pre_{i}>=k\) 的位置 \(i\),此时满足了限额要求,以后值低于 \(k\) 时会突变。
- 在位置 \(i\) 后再找第一个 \(pre_{i} < k\) 的位置 \(j\)。那么结果应当为 \(k + res[j]\);若位置 \(j\) 不存在,则结果为 \(pre[n]\)。
看似可以两次二分来找,但不要忘了,\(pre\) 数组是不单调的呀!!!
我的做法是:由于要枚举的所有 \(k\) 已知(均为 \(pre\) 中的元素),因此可以从大到小枚举所有 \(k\),并用两个 \(multiset\) 来分别动态维护 \(>=nowk\) 与 \(<nowk\) 的位置,再在两个 \(multiset\) 上各做一次二分即可。
那么现在就只剩下了一个问题:预处理出 \(res\) 数组。
每个 \(res_{i}\) \(O(n)\) 来求显然不现实。这里采用了值域树状数组 + 离散化来解决:
考虑倒序计算所有的 \(res_{i}\),并分析每个 \(res_{i}\) 怎么根据前面的结果来递推出来:对于当前的 \(a_{i}\):
- \(a[i] < 0\):显然等于上一次计算出的 \(res_{i}\)。
- 否则,需要计算出值第一次 \(<0\) 的位置:需要维护后面每个位置的 \(mn\),表示从该位置开头的所有前缀,变化量总和的最小值。可以用 \(mn = min(a[i], mn + a[i])\) 这个计算式来优美地递推得到。则问题等价于在后续位置找到第一个满足 \(a[i] + mn_{j} <= 0\) 的 位置 \(j\),则:\(res[i]=res[j]\)。可以将当前所有 \(mn\) 放入一个值域树状数组中,每次对于当前的 \(a[i]\),要找 \(mn_{j} <= -a[i]\) 的第一个位置 \(j\),由于树状数组维护的是对应 \(mn\) 的最左侧位置,直接调用 \(bit.querymin(mp[-a[i]])\) 即可。
总复杂度 \(O(nlogn)\),具体实现见代码。
\(update:\) 这里补一下正解的思路:只需要求原数组的最小子段和即可,但是感觉真的太巧妙了。这里来详细证明一下:
首先接续之前的一个证明:\(k\) 一定可以取得某个 \(pre_{i}\)。
然后展示一个示例变化过程:
发现取某个 \(pre_{i}\) 作为 \(k\) 时,可以取无 \(k\) 的限制时后续变化中的最小值。若这个最小值 \(<k\),则可以说明:在有 \(k\) 的限制时,在这个位置的实际值恰好为 \(k\),且后续变化仍保持原样,不会再受 \(k\) 的影响(因为此处为后续所有情况可能减到的最小值,因此后面不会再出现减到 \(<k\) 的情况,自然也就不需要再考虑 \(k\) 的约束)。那么这样的变化,本质上就是去除了中间的某段子数组再用后续操作的过程(如图所示)。而前面的操作也保持不变,因此,添加 \(k\) 的限制,可以等价于去除了某一段子数组再进行无 \(k\) 限制的操作。即操作等价于:\(k\) 取某个 \(pre_{i-1}\),去除以 \(i\) 为左端点的某个子数组(也可不去除),只用剩下的后缀操作。显然要想保证结果最大,让这个子数组总和最小即可,因此问题等价于找原数组的某个最小子段和出现的位置。具体细节见代码。