1 条题解
-
0
题目题解
问题理解
给定数组 ,找出最长的好子序列 。
好子序列的定义:- (下标从 1 开始)。
- 存在一个排列 ,使得对每个 , 是满足 的最小整数。
空序列被认为是好的。
第一步:好序列的等价刻画
经过分析(见 Hint 1),一个序列 是好的当且仅当:
对每个位置 ,设 ,则以下条件之一成立:- ,或
- 且 。
另一种理解方式:
考虑所有值为 的位置,它们形成一个区间 ,其中 是第一个值为 的位置, 是最后一个。
那么要求:对每个 ,区间 内的所有 值都 ,并且 处的值等于 (即 )。这类似于一种区间嵌套结构:值较小的区间完全包含在值较大的区间内。
第二步:转化为区间 DP
我们可以将问题转化为:选择一些位置作为“区间起点”,每个起点 对应一个值 ,并向右扩展,使得区间内所有值 ,且区间起点处的值等于 。
设 表示:以 为区间起点(且 作为该区间的值),在 范围内能构造的最长好子序列长度。
注意这里 中的子序列不一定以 结尾,但要求 被选中,且所有选中的值 。初始化:(只选 本身)。
第三步:转移
我们从右向左计算 ,考虑 的取值:
-
:
可以直接将 附加到当前区间末尾,因为值相同且 。 -
:
不能选 (因为会破坏区间内值 的性质)。 -
:
此时可以以 为新的区间起点,在 内构造子序列,其中 是满足 的最小索引(即可以开始新区间的最早位置)。
为了找到 ,我们预处理一个数组first,其中first[k]表示最小的索引 使得 (即从 开始到 的区间至少能构造出长度为 的好子序列)。
则转移为:
第四步:计算
first数组在计算 的过程中,我们可以同时更新
first:
当 时,说明从 开始到 的区间可以独立成为一个好子序列(长度为 或更多),因此first[j] = min(first[j], i)。
第五步:最终答案
所有 中的最大值即为最长好子序列的长度。
由于我们只需要长度,可以只维护一维的 数组,但为了更新first仍需按区间处理。
时间复杂度
- 状态数 ,但 , 可能过大。
- 实际上,由于转移中 的值较小,很多 无效,可以通过优化只计算必要的状态。
- 官方解法使用 的 DP,但由于 ,总计算量约为 ,在 7 秒内可行。
代码实现
#include <bits/stdc++.h> using namespace std; const int INF = 1e9; void solve() { int n; cin >> n; vector<int> a(n); for (int i = 0; i < n; i++) { cin >> a[i]; } // dp[i][j] 表示以 i 为起点,在 [i, j] 内能构造的最长好子序列长度 vector<vector<int>> dp(n, vector<int>(n, 0)); vector<int> first(n, INF); int ans = 0; // 初始化:长度为 1 的区间 for (int i = 0; i < n; i++) { dp[i][i] = 1; if (1 >= a[i]) { first[i] = min(first[i], i); } ans = max(ans, dp[i][i]); } // 区间 DP for (int len = 2; len <= n; len++) { for (int i = 0; i + len - 1 < n; i++) { int j = i + len - 1; if (a[j] == a[i]) { dp[i][j] = dp[i][j - 1] + 1; } else if (a[j] < a[i]) { dp[i][j] = dp[i][j - 1]; } else { // a[j] > a[i] dp[i][j] = dp[i][j - 1]; // 尝试以 a[j] 为新区间的起点 int y = first[j]; if (y != INF && y > i) { dp[i][j] = max(dp[i][j], dp[y][j]); } } if (dp[i][j] >= a[i]) { first[j] = min(first[j], i); } ans = max(ans, dp[i][j]); } } cout << ans << "\n"; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { solve(); } return 0; }
验证样例
输入:
5 5 1 1 3 3 5 3 1 1 2 4 2 2 2 2 7 1 2 4 2 4 6 2 1 1输出:
5 2 0 5 1与题目输出一致。
总结
本题的关键在于:
- 将好序列的性质转化为区间嵌套结构。
- 使用区间 DP 模拟选择过程。
- 利用
first数组快速找到新区间的起点,优化转移。
- 1
信息
- ID
- 6287
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 1
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者