1 条题解
-
0
题目分析
这道题要求计算通过特定操作能够获得的所有不同非空序列的数量。操作规则是:选择一个严格前缀最大值位置,如果该位置的值不为1,则将其值减1后添加到序列末尾,然后删除序列的前s个元素。
算法思路
核心思想
使用动态规划结合单调栈来统计所有可能的序列。主要分为几种情况处理:
1. m = 1 的特判:序列全为1,只能得到n个不同的序列(原序列的各个前缀)
2. m ≤ 2 的特判:通过简单计数公式计算
3. 一般情况 (m ≥ 3):分为两个子问题求解
主要算法
1. 单调栈预处理
-
计算每个位置的 nxt[i]:右边第一个大于 a[i] 的位置
-
计算每个位置的 lst[i]:左边第一个大于等于 a[i] 的位置
-
用于快速找到严格前缀最大值之间的关系
2. 动态规划求解 (solve1) 处理不涉及序列扩展的情况,使用二维DP:
-
dp[i][0/1] 表示考虑前i个元素,是否已经进行过特定操作的状态数
-
状态转移考虑两种选择:
-
直接移动到下一个位置
-
跳到下一个严格前缀最大值位置
-
3. 扩展序列求解 (solve2) 处理序列可能扩展的情况:
-
将原序列扩展为2倍长度:a[i] 和 a[i]-1
-
使用多个DP数组和前缀和数组进行复杂的状态转移
-
分别计算不同起始位置p的贡献
算法标签
-
动态规划 - 核心算法,用于状态转移和计数
-
单调栈 - 预处理严格前缀最大值关系
-
组合计数 - 统计所有可能的序列
-
前缀和优化 - 优化DP状态转移
代码关键部分解析
单调栈预处理
sta[tot = 1] = n + 1; a[n + 1] = m + 1; for (int i = n; i; i--) { while (a[sta[tot]] <= a[i]) lst[sta[tot--]] = i; nxt[i] = sta[tot]; sta[++tot] = i; }
主要DP转移 (solve1)
memset(dp, 0, sizeof(dp)); dp[1][0] = 1; for (int i = 1; i <= n; i++) { for (int j = 0; j < 2; j++) { if (!j || a[i] == 1) add(dp[i + 1][j | (a[i] == 1)], dp[i][j]); add(dp[nxt[i]][j], dp[i][j]); } }
复杂状态转移 (solve2)
for (int p = 1; p <= n; p++) { if (a[p] == 1) continue; // 使用f, g, ff, gg等多个DP数组 // 结合前缀和pre, ppre进行优化 // 计算从位置p开始的所有可能扩展 }
复杂度分析
-
时间复杂度:O(n²) - 主要来自solve2中对每个起始位置p的O(n)处理
-
空间复杂度:O(n) - 使用多个大小为O(n)的数组
总结
这道题通过巧妙的动态规划设计,结合单调栈预处理,成功解决了序列操作计数这一复杂问题。算法充分利用了严格前缀最大值的性质,通过状态压缩和前缀和优化,在合理的时间复杂度内完成了计数任务。
-
- 1
信息
- ID
- 3353
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者