3524. 求出数组的 X 值 I
考点
- 子数组之间的状态转移
- 乘法取模
思路
一、问题重述与等价转化
给定一个整数数组 \[ \text{nums} = [a_1, a_2, \dots, a_n] \] 以及一个正整数 \(k\)。
允许从数组中删除一个前缀和一个后缀(均可为空),剩下的部分必须是一个非空连续子数组。 对所有可能得到的子数组,统计其元素乘积对 \(k\) 取模后的结果分布。
形式化地,对所有子数组 \([l, r]\)(\(1 \le l \le r \le n\)),定义 \[ P(l,r) = \prod_{i=l}^r a_i \] 需要统计每个 \[ x \in \{0,1,\dots,k-1\} \] 满足 \[ P(l,r) \bmod k = x \] 的子数组数量。
二、核心数学性质
整个问题仅依赖以下基本模运算性质: \[ (a \cdot b) \bmod k = \big( (a \bmod k)\cdot(b \bmod k) \big) \bmod k \] 因此,在计算子数组乘积时:
- 不需要关心真实乘积的大小
- 只需要维护“乘积对 \(k\) 的余数”
三、问题建模:按右端点分类的动态规划
3.1 状态定义
设在处理到数组第 \(i\) 个元素时(即右端点为 \(i\)): \[ \text{dp}_i[r] = \text{以 } i \text{ 结尾的子数组中,乘积对 } k \text{ 取模为 } r \text{ 的个数} \] 其中: \[ r \in \{0,1,\dots,k-1\} \] 最终答案数组为: \[ \text{ans}[r] = \sum_{i=1}^n \text{dp}_i[r] \]
3.2 状态转移的来源
考虑第 \(i\) 个元素 \(a_i\),记: \[ x = a_i \bmod k \] 以 \(i\) 为右端点的子数组来源只有两类:
情况一:新开子数组
只包含当前元素的子数组: \[ [i,i] \] 其乘积余数为: \[ x \] 贡献为: \[ \text{dp}_i[x] \mathrel{+}= 1 \]
情况二:扩展已有子数组
对所有以 \(i-1\) 结尾、乘积余数为 \(j\) 的子数组: \[ \text{dp}_{i-1}[j] \] 将其右端点扩展到 \(i\),新余数为: \[ (j \cdot x) \bmod k \] 转移方程: \[ \text{dp}_i[(j\cdot x)\bmod k] \mathrel{+}= \text{dp}_{i-1}[j] \]
3.3 完整状态转移方程
综合两类情况,可得: \[ \boxed{ \begin{aligned} \text{dp}_i[r] &= \mathbf{1}_{r=x} + \sum_{j=0}^{k-1} \Big[ (j\cdot x)\bmod k = r \Big] \cdot \text{dp}_{i-1}[j] \end{aligned} } \] 其中 \(\mathbf{1}_{r=x}\) 表示当 \(r=x\) 时取 1,否则取 0。
四、算法流程
- 使用长度为 \(k\) 的数组
pre_cnt表示 \(\text{dp}_{i-1}\) - 使用长度为 \(k\) 的数组
cur_cnt表示 \(\text{dp}_i\) - 对数组从左到右遍历:
- 清空
cur_cnt - 加入新子数组
[i,i] - 扩展所有旧子数组
- 将
cur_cnt累加到答案数组 - 交换
pre_cnt与cur_cnt
- 清空
五、复杂度分析
- 状态数:\(k \le 5\)
- 每个元素进行一次 \(k\) 规模转移
时间复杂度: \[ O(n \cdot k) \] 空间复杂度: \[ O(k) \]
六、AC代码
1 | class Solution { |