1 条题解
-
0
一、题目重述(核心)
我们有一个已知数组 ( a_1, a_2, \dots, a_n ),满足:
[ \max(|a_i|) \le \sum_{i=1}^n a_i ]
要求构造新的数组 ( b_1, b_2, \dots, b_m ),满足:
- ( a ) 是 ( b ) 的子序列(不一定连续,但顺序一致)。
- (\sum b = \sum a)。
- 在满足 1、2 的所有 ( b ) 中,最大子段和(boredom)最小。
- 在 boredom 最小的基础上,长度 ( m ) 最小。
- 对每个这样的最优 ( b ),计算 ( a ) 作为子序列在 ( b ) 中的出现次数(子序列匹配计数),称为该 ( b ) 的 value。
- 求所有可能的最优 ( b ) 的 value 之和,对 ( 998244353 ) 取模。
二、理解最优 ( b ) 的结构
设 ( S = \sum a )。
原题条件 (\max |a_i| \le S) 保证了 ( S \ge 0 )。
1. 最小可能的 boredom
最大子段和最小是多少?
任何子段和 ≤ 数组总和 ( S ),并且至少包含一个元素。
可以构造 ( b ) 使最大子段和 = ( S ) 吗?
可以:如果 ( b ) 所有元素都是非负的,那么最大子段和 = ( S )。可以更小吗?
比如 ( b = [S] ) 时,最大子段和 = ( S ),所以 ( S ) 是下界吗?不一定,因为要包含 ( a ) 作为子序列。由于 ( a ) 中可能有负数,但总和 ( S ) 是非负的,所以包含 ( a ) 的子序列的最大子段和至少是 ( \max a_i ) 吗?其实我们看 ( b ) 的构造方式。
关键观察:
在最优构造中,boredom = ( S )。为什么?
因为我们可以构造 ( b ) 为 ( a ) 加上一些辅助元素,使总和不变,且所有元素非负(除保留 ( a ) 中的负数),但一旦有负数,最大子段和可能大于 ( S ) 吗?
实际上,如果我们把负号放在两端,中间非负,最大子段和仍然是某一段非负的和。
但如果我们希望 boredom 尽量小,一个简单方法是把 ( a ) 拆成若干段非负的连续段,段内求和作为新元素,负数单独成元素,但这样可能让负数出现在中间导致更大的最大子段和。其实已知结论(来自官方题解):
最优的 boredom = ( S ),且构造方式如下:- 把 ( a ) 从左到右处理,维护当前连续非负和 cur。
- 当遇到负数时,若 cur + 这个负数 ≥ 0,则继续累积;否则,cur 单独作为一个正段,负数单独作为一段,然后 cur 重置为 0 或这个负数(如果负数大于 cur 的绝对值)。 实际上标准解法最终最优的 ( b ) 长度 = ( n + ) 一些插入的辅助元素,且每个元素 ≤ S,总和 = S,每个元素 ≥ -S,且最大子段和 = S。
三、转化为组合问题
对最优 ( b ) 的要求:
- 子序列包含 ( a )
- 总和 = ( S )
- 每个元素 ∈ ([-S, S])
- 所有元素的和 = ( S )
- 最大子段和 = ( S )
最大子段和 = ( S ) 等价于:存在一个子段(连续)和为 ( S ),且其他子段和 ≤ ( S )。由于总和也是 ( S ),这意味着 整个数组的最大子段和等于总和,即从某位置到结尾的和等于 S,或从头到某位置的和等于 S,或整个数组的和等于 S(显然成立)。更准确地说,这意味着数组在某个前缀或后缀达到最大,且其他子段不能更大。
另一种等价条件(常见于这种题):
数组的最大子段和 = ( S ) ⇔ 存在一个位置,使得所有前缀和 ≤ S 且所有后缀和 ≤ S,并且某个前缀或后缀等于 S。但这里因为总和 = S,所以只要所有子段和 ≤ S,且存在某个子段和 = S 即可。
四、构造最优 ( b ) 的标准形式
官方题解结论:
最优 ( b ) 可以构造成如下形式:从左到右扫描 ( a ),维护当前非负累加和 cur。
- 若 a[i] ≥ 0,cur += a[i]
- 若 a[i] < 0:
- 若 cur + a[i] ≥ 0,则 cur += a[i](不单独分段)
- 若 cur + a[i] < 0,则把 cur 作为一个正元素,a[i] 作为一个负元素,cur 重置为 0(表示新开始)。
这样构造出来的 b 长度最小,并且最大子段和 = S。
但问题是,这样构造的 b 是唯一的吗?
不是,因为对于非负累加和 cur,我们可以在任何位置拆分 cur 为两个正数,只要它们加起来等于 cur,仍然满足条件。
这就是计数的地方。
五、动态规划与单调队列优化
我们要求所有可能的最优 b 中,a 作为子序列的出现次数之和。
等价于:我们按照上述构造方式,在遇到 cur 段时,可以选择把 cur 拆成若干正数,它们的顺序是固定的(因为必须保持 a 中顺序),但是这些正数之间可以插入 0 吗?
插入 0 不影响子序列匹配,但会影响出现次数计数。其实这是一个经典的子序列自动机 + 分段组合问题。
我们定义状态:
扫描 a 的过程中,维护当前 b 的末尾指针位置?但直接做指数级。
六、标程分析
标程中:
Q.push_back(Node(0, 1, 1)), Q.push_back(Node(1, 1, p));p是总和 ( S )。Node包含:f:段类型(其实这里是分段后的索引?)g:该段的贡献计数(modint)len:该段的长度(在值域意义上)
Q是 deque,维护当前可能的段。遍历 ( a_i ):
- 若 a[i] ≥ 0:增加正长度,更新 Q 尾部
- 若 a[i] < 0:减少长度,更新 Q 头部
v是当前段编号。gv和gz分别表示当前段和另一个段的累计贡献。tv和tz是中间变量。最后答案是
Q.back().g + (r == p ? tv : tz)。
这里
r表示当前剩余可分配的正长度,l表示左边界。实际上这是在维护所有可能的最优 b 中,a 作为子序列的出现次数,用单调队列优化转移,因为每次增减长度是区间操作。
七、总结算法流程
- 读入 a,计算 S = sum(a),验证 max|a| ≤ S。
- 用双端队列维护当前可能的正段长度(值域 ≤ S),每个段有贡献计数。
- 从左到右处理 a[i]:
- 如果 a[i] ≥ 0:
从队列尾部减去对应长度,更新 gv/gz,
更新 l, r,
若 l > r,说明必须新开一段,v++,重置变量。 - 如果 a[i] < 0:
从队列头部减去对应长度,更新 gv/gz,
更新 l, r,
同样检查 l > r 则新开一段。
- 如果 a[i] ≥ 0:
- 每一步要维护 gz += -tz * (a[i] % MOD) 等,其实是在计算组合数贡献。
- 最后队列尾部的 g 加上特殊情况贡献即为答案。
八、复杂度
每个 a[i] 最多进入/离开队列一次,总复杂度 O(n)。
九、关键点
- 最优 b 的唯一自由度是:非负段可以拆分成若干正数,不同拆法产生不同 b,不同 b 中 a 作为子序列的匹配次数不同。
- 用单调队列维护当前所有可能的拆分长度,结合前缀和差分统计贡献。
- modint 类处理取模。
- 1
信息
- ID
- 6621
- 时间
- 3000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者