信息学奥赛一本通题目解析:1259:【例9.3】求最长不下降序列

【题目描述】

设有由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[]来存储状态转移的路径,方便在得到最长子序列的长度后进行回溯输出序列。  
 */

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值