0%

CF Round 1100 Div12 C2 - We Be Flipping (Hard Version)

题目大意

给一个长度为 nn 的非零整数数组 aa 。一次操作可以选择一个当前为正数的位置 ii ,然后把前缀 1..i1..i 的所有数全部取相反数。

Hard Version 要求输出不超过 nn 次操作,使最终数组的元素和最大。

数据范围:

  • 1t1041 \le t \le 10^4

  • 2n21052 \le n \le 2 \cdot 10^5

  • 109ai109,ai0-10^9 \le a_i \le 10^9, a_i \ne 0

  • 所有测试的 nn 之和不超过 21052 \cdot 10^5

样例里第三组 [1,3,2,1,10][1,-3,2,-1,10] 可以操作 1,31,3 ,变成 [1,3,2,1,10][1,3,-2,-1,10] ,最终和为 1111

思路讲解

一句话

先把连续同号段压成块,从左到右消掉能稳定处理的正块;最后那个正块要选一个最小正数 mnIdmnId 做收尾,C2 和 C1 的差别其实就在这里:C1 已经会了 ++-+ \rightarrow +- \rightarrow -- ,那 C2 只差最后一瞬间把负号翻回正号。

从 C1 到 C2 的那个小火花

C1 的关键感觉是:遇到相邻的异号结构时,前缀翻转并不是单纯“把前面全部翻掉”,它还会让边界附近的符号发生迁移。

核心观察:如果 C1 已经想明白 ++-+ \rightarrow +- \rightarrow -- ,那么 C2 里“正负号的一刹那”也就出来了。最后我们不只是把前面扫成目标形态,还可以在收尾时让一个负号重新变成正号。

具体落到最后一个正块,取这个块里值最小的位置 mnIdmnId 。收尾三步可以看成:

1
2
3
// +-+(mnId)
// --+(mnId)
// ++-(mnId)

也就是说,先借左侧边界把局面推成可控的 +++-+ ,再让它变成 +--+ ,最后点到 mnIdmnId ,把需要留下来的负号翻回正号。这个 “最后补一刀” 是 Hard Version 相比 Easy Version 最容易漏掉的地方。

为什么要压连续同号块

同号连续的一整段在前缀翻转下总是一起改变符号,真正发生结构变化的是正负块的交界。所以先把数组压成若干块:

  • 正块:里面全是正数。

  • 负块:里面全是负数。

  • 相邻块符号一定相反。

这样操作的对象就从单个数变成了“块边界”。普通正块可以用块尾和前一块边界去处理;最后留下来的正块单独处理,因为它决定最大和时要不要牺牲一个正数。

最后一块怎么选

如果全是负数,没法操作,答案就是 00 次。如果全是正数,和已经最大,也不用操作。

否则我们从最后一个正块往左看。对某个正数 aia_i ,它前面已经有一些负数绝对值会被翻成正贡献;如果这些负数的收益足够覆盖牺牲这个正数,就可以把它作为最后收尾的位置。

代码里用 presum 记录前缀负数绝对值之和,判断形如:

1
2
3
if (A[pos] < negative_sum_before_pos) {
diff = negative_sum_before_pos - A[pos];
}

diff 最大的那个块作为最后一块。直觉上就是:最后牺牲哪个正数最划算,或者说把哪段前缀整理完以后总和最大。

操作怎么输出

对最后一块之前的每个正块:

  • 先操作这个正块的块尾。

  • 再操作它前一个位置,也就是左侧负块的尾部。

这相当于沿着同号块从左到右做“符号清扫”。

到了最后一块,找块内最小的正数 mnIdmnId

  • 如果 mnIdmnId 正好在块首,就直接操作它。

  • 否则按刚才那组三步收尾:mnId - 1block_front - 1mnId

这一套操作次数仍然不会超过 nn ,因为每个块只贡献常数次操作,而块数不超过元素数。

这题值得记住的点

C2 不是把 C1 推倒重做,而是把 C1 的局部符号变换再多看一眼: ++-+ \rightarrow +- \rightarrow -- 已经说明边界可以被“搬运”,Hard 版只是要求你在最大和目标下,选一个最划算的收尾点。

这个观察特别适合以后复盘:Easy Version 给的是能不能做,Hard Version 要的是在哪里停最赚。

AC 代码

AC 提交链接

源码较长,折叠在下面。

心路历程(WA,TLE,MLE……)

  • 这题最关键的不是代码长,而是把 C1 的局部变换真正吃透。只要意识到 ++-+ \rightarrow +- \rightarrow -- 还能在最后收尾时把负号翻回正号,Hard 的“最大和”就从玄学变成了选点问题。

  • 容易漏的点是最后一块不能随便选块尾,要选块内最小的正数 mnIdmnId ,否则可能多牺牲了一个本来可以保住的正贡献。

  • 另一个坑是全负和全正这两个边界。全负时没有合法操作,全正时已经最大;这两个都应直接输出 00

附件

暂无附件。