【题目描述】
设有由n(1≤n≤200)个不相同的整数组成的数列,记为:b(1)、b(2)、……、b(n)若存在i1<i2<i3<…<ie且有b(i1)<=b(i2)<=…<=b(ie)则称为长度为e的不下降序列。程序要求,当原数列出之后,求出最长的不下降序列。
例如13,7,9,16,38,24,37,18,44,19,21,22,63,15
。例中13,16,18,19,21,22,63
就是一个长度为77的不下降序列,同时也有7 ,9,16,18,19,21,22,63
组成的长度为88的不下降序列。
【输入】
第一行为n,第二行为用空格隔开的n个整数。
【输出】
第一行为输出最大个数max(形式见样例);
第二行为max个整数形成的不下降序列,答案可能不唯一,输出一种就可以了,本题进行特殊评测。
【输入样例】
14
13 7 9 16 38 24 37 18 44 19 21 22 63 15
【输出样例】
max=8
7 9 16 18 19 21 22 63
思路分析:
求解最长不下降子序列(Longest Non-Decreasing Subsequence, LNDS)问题的动态规划解法。下面将详细解释其状态设计、策略和状态转移方程。
状态设计
- 状态定义:
f[i]
表示以b[i]
结尾的最长不下降子序列的长度。这意味着在考虑位置i
时,我们只关心以这个位置结尾的子序列,而不考虑其它不以b[i]
结尾的子序列。 - 附加状态:
pre[i]
表示在构建以b[i]
结尾的最长不下降子序列时,b[i]
前面的那个元素的位置。这不是必须的,但它对于最后回溯并输出实际的最长不下降子序列非常有用。
策略
- 填充顺序:从左到右,即从序列的起始位置到结束位置。这是因为我们需要知道前面的所有状态才能正确地计算当前状态。
- 比较与更新:对于每个位置
i
,比较所有在它之前的元素b[j]
(j < i
),如果b[j] <= b[i]
,则说明b[i]
可以接在以b[j]
结尾的不下降子序列后面,从而形成一个更长的不下降子序列。更新f[i]
和pre[i]
以反映这种可能性。
状态转移方程
- 方程:
f[i] = max(f[i], f[j] + 1)
,其中j
遍历所有小于i
且满足b[j] <= b[i]
的位置。f[i]
初始化为 1,因为每个单独的元素都可以被视为一个长度为 1 的不下降子序列。- 对于每个
i
,我们遍历所有在它之前的元素j
,并检查是否可以将b[i]
添加到以b[j]
结尾的子序列中。如果可以,我们更新f[i]
为f[j] + 1
(如果这会导致更长的子序列)。 - 还使用
pre[i]
来记录用于更新f[i]
的那个j
的值,这样最后就可以通过回溯来找到实际的最长不下降子序列。
最后,遍历所有的 f[i]
来找到最长的子序列的长度以及它结束的位置。然后,使用 pre
数组从这个位置开始回溯,直到到达序列的起点,这样就可以输出整个最长不下降子序列了。
#include <iostream>
using namespace std;
const int MAXN = 205; // 定义常数MAXN为最大数组长度
int b[MAXN]; // 存储输入的序列
int f[MAXN]; // f[i]表示以b[i]为结尾的最长不下降子序列的长度
int pre[MAXN]; // 用于回溯,pre[i]表示f[i]从哪一个前驱状态转移而来
int main() {
int n; // 输入序列的长度
cin >> n;
// 读入序列
for (int i = 1; i <= n; i++)
cin >> b[i];
// 动态规划计算每个位置的最长不下降子序列长度
for (int i = 1; i <= n; i++) {
f[i] = 1; // 单个元素本身就是一个长度为1的不下降子序列
// 在当前位置之前查找可以转移来的最长不下降子序列
for (int j = 1; j < i; j++) {
if (b[j] <= b[i] && f[j] + 1 > f[i]) {
// 如果找到比当前f[i]更长的序列,则更新f[i]和对应的转移来源pre[i]
f[i] = f[j] + 1;
pre[i] = j;
}
}
}
// 找到整个序列中最长的不下降子序列的长度和终点位置
int ans = 0, k; // ans用于记录最长长度,k用于记录最长序列的终点位置
for (int i = 1; i <= n; i++)
if (f[i] > ans) {
ans = f[i]; // 更新最长长度
k = i; // 记录最长序列的终点位置
}
// 输出最长不下降子序列的长度
cout << "max=" << ans << endl;
// 根据pre数组回溯并输出最长的不下降子序列
int a[MAXN], cnt = 0; // a用于存储回溯的结果,cnt用于记录当前结果的长度
while (k) { // 一直回溯到序列的起点
a[++cnt] = b[k]; // 将元素存入结果数组
k = pre[k]; // 移动到前驱状态的位置
}
// 由于我们是从后往前追溯的,因此结果数组需要逆序输出
for (int i = cnt; i >= 1; i--)
cout << a[i] << " "; // 输出最长不下降子序列
cout << endl; // 输出换行
return 0; // 程序正常结束
}
/* 使用动态规划算法求解最长不下降子序列问题。定义状态f[i]表示以b[i]结尾的最长不下降子序列的长度,
* 则状态转移方程为:f[i] = max{f[j] + 1},其中j < i 且 b[j] <= b[i]。
*
* 同时使用数组pre[]来存储状态转移的路径,方便在得到最长子序列的长度后进行回溯输出序列。
*/