本文最后更新于 8 个月前,文中所描述的信息可能已发生改变。
E. Block Sequence 解题思路
核心问题分析与数学转化
本题要求找到使给定序列 成为“美丽序列”所需的最小删除次数。一个美丽序列由一系列“块”构成,每个块以其长度开头,后跟该长度的元素。例如,。
我们的目标是最大化保留下来的美丽子序列的长度 。最小删除次数 与最大保留长度的关系为:
其中 是原始序列的长度。因此,问题转化为:计算原序列 中最长的美丽子序列的长度。
2. 动态规划状态设计
由于我们需要在 复杂度内求解最长子序列问题,我们选择使用动态规划 (DP)。
一个块 的总长度是 . 要构建最长的美丽序列,我们必须从当前位置 开始,选择:
- 跳过当前元素 。
- 将当前元素 作为新块的长度 ,并检查该块是否合法。
我们定义 为:
: 在原始序列中,从索引 到 的后缀中,能够构建出的最长美丽子序列的长度。
状态转移的机制分析
我们采用从后向前的递推方式,即从 遍历至 $ 0$。
跳过 : 如果 不作为任何块的开头(即被删除),则 至少等于 :
以 为长度 构成块: 设 。这个块包含 本身和接下来的 个元素。
- 块的结束索引: 。
- 块的总长度: 。
- 下一个块的起始索引: 。
合法性检查: 只有当整个块 不越界时,这种选择才合法。即 。
如果合法,则新的子序列长度为当前块的长度,加上从 开始的最长美丽子序列长度 :
最终转移: 是两种选择(跳过 或以 构成新块)中的最大值:
其中,若 ,则第二个选项不参与 运算。
边界条件
到 :由于索引 可能超出 ,我们将 数组扩展到 ,并将 作为基本情况(空后缀的长度为 )。
最终答案: 。
CPP 代码实现
代码中采用了从后向前的线性 DP 求解,时间复杂度为 。
cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
void solve() {
int n;
// 读取序列长度 N
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
// dp[i]: 从索引 i 开始的最长美丽子序列的长度
// 数组大小为 n + 7, 以确保 next_start_idx 不越界
vector<int> dp(n + 7, 0);
// 从后往前进行 DP 求解
for (int i = n - 1; i >= 0; i--) {
int L = a[i];
// 下一个块的理论起始索引 j = i + L + 1
int next_start_idx = i + L + 1;
// 选项 1: 跳过 a[i],长度为 dp[i+1]
dp[i] = dp[i + 1];
// 检查以 a[i] 为长度 L 的块是否合法 (块结束索引 i+L < n)
if (i + L < n) {
// 选项 2: 以 a[i] 构成新块。
// 当前块总长度 (L + 1) + 从 next_start_idx 开始的最长长度 dp[j]
int length_jump = (L + 1) + dp[next_start_idx];
// 取两种选项的最大值
dp[i] = max(dp[i], length_jump);
}
}
// 最小删除次数 = 总长度 N - 最大保留长度 dp[0]
cout << n - dp[0] << '\n';
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}