用 O(n^2) 动态规划维护每个位置结尾的最长不下降子序列长度和方案数,最后汇总最优结尾。
OJ: luogu
题目 ID: P2362
难度:普及-
标签:dplis动态规划
日期: 2026-05-31 15:31
题意
给出多组木桩高度序列。
每组都要从中按原顺序选出若干根木桩,使高度构成不下降序列。
要求输出:
- 最长不下降子序列长度;
- 达到这个长度的方案数。
思路
最直接的教学版做法是枚举所有子序列:
cpp
// brute.cpp:枚举所有子序列,统计最长不下降子序列长度和方案数,作为教学版和对拍基准程序。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 25;
int T;
int n;
int a[MAXN];
int best_len;
long long ways;
void dfs(int pos, int last_value, int cur_len) {
if (pos > n) {
if (cur_len > best_len) {
best_len = cur_len;
ways = 1;
}
else if (cur_len == best_len) {
ways++;
}
return;
}
// 不选当前位置
dfs(pos + 1, last_value, cur_len);
// 选当前位置,必须保持不下降
if (a[pos] >= last_value) {
dfs(pos + 1, a[pos], cur_len + 1);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> T;
while (T--) {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
best_len = 0;
ways = 0;
dfs(1, -1000000000, 0);
cout << best_len << ' ' << ways << '\n';
}
return 0;
}但真正提交时,用
设:
dp[i]:以i结尾的最长不下降子序列长度cnt[i]:达到dp[i]的方案数
初始化:
dp[i] = 1cnt[i] = 1
DP 公式
把序列记为
当
最后令
然后枚举 j < i:
如果 a[j] <= a[i],就可以从 j 转移到 i。
- 若
dp[j] + 1 > dp[i],说明找到更优解,直接覆盖长度和方案数; - 若
dp[j] + 1 == dp[i],说明又找到一种同样优的接法,把方案数累加。
最后先求出全局最长长度,再把所有达到这个长度的结尾方案数加起来。
公式解释:dp_i 只关心最后选中的木桩是 i,因为前面的合法序列只需要满足高度不超过 a_i。当从 j 接到 i 得到更长长度时,旧方案全部失效,所以方案数改成 cnt_j;如果长度相同,就说明多了一批同样最优的结尾方案,要把方案数累加。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 25;
int T;
int n;
int a[MAXN];
int dp[MAXN]; // dp[i]:以 i 结尾的最长不下降子序列长度
long long cnt[MAXN]; // cnt[i]:达到 dp[i] 的方案数
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> T;
while (T--) {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
dp[i] = 1;
cnt[i] = 1;
}
int best_len = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j < i; j++) {
if (a[j] <= a[i]) {
if (dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1;
cnt[i] = cnt[j];
}
else if (dp[j] + 1 == dp[i]) {
cnt[i] += cnt[j];
}
}
}
best_len = max(best_len, dp[i]);
}
long long ways = 0;
for (int i = 1; i <= n; i++) {
if (dp[i] == best_len) {
ways += cnt[i];
}
}
cout << best_len << ' ' << ways << '\n';
}
return 0;
}复杂度
- 时间复杂度:
- 空间复杂度:
总结
这题是经典的最长不下降子序列 DP 扩展:
在维护长度的同时,再维护“达到这个最优长度的方案数”即可。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
