1955. 统计特殊子序列的数目
考点
- 状态机DP
思路
状态设计与转移方程说明
问题结构概述
给定一个仅由数字 \(0,1,2\) 构成的数组,需要统计满足以下形式的非空子序列数量: \[ \underbrace{0\cdots 0}_{\ge 1} \underbrace{1\cdots 1}_{\ge 1} \underbrace{2\cdots 2}_{\ge 1} \] 即子序列必须严格按 0 段 → 1 段 → 2 段 的顺序构成,每一段至少包含一个元素。
该问题的核心在于:每个合法子序列在构造过程中,只会处于三种阶段之一,因此可以用常数个状态完成动态规划。
状态定义
在扫描数组前 \(i\) 个元素后,定义以下三个状态变量:
- \(a_i\):仅由若干个 \(0\) 组成的非空子序列个数
- \(b_i\):形如 \(0\cdots0\,1\cdots1\) 的非空子序列个数
- \(c_i\):形如 \(0\cdots0\,1\cdots1\,2\cdots2\) 的非空子序列个数
其中,\(c_i\) 即为最终所求答案。
初始状态为: \[ a_0 = b_0 = c_0 = 0 \]
状态转移思想
对数组进行一次线性扫描。设当前读入的元素为 \(x\),每个已有子序列对 \(x\) 都只有两种选择:
- 不选当前元素:子序列保持不变
- 选当前元素:若满足阶段约束,则扩展该子序列
这正是状态“倍增 + 引入”的来源。
状态转移方程
情况一:当前元素为 \(0\)
- 已有的每一个 \(0\)-子序列,都可以选择“选或不选”当前的 \(0\),贡献为 \(2a\)
- 还可以从空序列新生成一个只包含当前 \(0\) 的子序列,贡献为 \(+1\)
因此: \[ a \leftarrow 2a + 1 \] 此时不可能生成包含 \(1\) 或 \(2\) 的合法序列: \[ b, c \text{ 保持不变} \]
情况二:当前元素为 \(1\)
- 每一个已有的 \(01\)-子序列可以选择是否追加该 \(1\),贡献为 \(2b\)
- 任意一个纯 \(0\)-子序列都可以接上该 \(1\),形成新的 \(01\)-子序列,贡献为 \(+a\)
因此: \[ b \leftarrow 2b + a \] 状态 \(a, c\) 不发生变化。
情况三:当前元素为 \(2\)
- 每一个已有的 \(012\)-子序列可以选择是否追加该 \(2\),贡献为 \(2c\)
- 任意一个 \(01\)-子序列都可以接上该 \(2\),形成新的合法序列,贡献为 \(+b\)
因此: \[ c \leftarrow 2c + b \] 状态 \(a, b\) 不发生变化。
完整转移汇总
在模 \(M = 10^9 + 7\) 意义下,状态转移可统一写为: \[ \begin{cases} a = 2a + 1, & \text{if } x = 0 \\ b = 2b + a, & \text{if } x = 1 \\ c = 2c + b, & \text{if } x = 2 \end{cases} \quad (\bmod\ M) \]
AC代码
1 | class Solution { |