题解:AcWing 1016 最大上升子序列和

【题目来源】

AcWing:1016 最大上升子序列和 - AcWing题库

【题目描述】

一个数的序列 \(b_i\),当 \(b_1\lt b_2\lt \dots \lt b_S\) 的时候,我们称这个序列是上升的。

对于给定的一个序列(\(a_1,a_2,\dots,a_N\)),我们可以得到一些上升的子序列(\(a_{i_1},a_{i_2},\dots,a_{i_K}\)),这里 \(1\le i_1\lt i_2 \lt \dots \lt i_K\le N\)

比如,对于序列(\(1,7,3,5,9,4,8\)),有它的一些上升子序列,如(\(1,7\)),(\(3,4,8\))等等。

这些子序列中和最大为\(18\),为子序列(\(1,3,5,9\))的和。

你的任务,就是对于给定的序列,求出最大上升子序列和。

注意,最长的上升子序列的和不一定是最大的,比如序列(\(100,1,2,3\))的最大上升子序列和为\(100\),而最长上升子序列为(\(1,2,3\))。

【输入】

输入的第一行是序列的长度\(N\)

第二行给出序列中的\(N\)个整数,这些整数的取值范围都在\(0\)\(10000\)(可能重复)。

【输出】

输出一个整数,表示最大上升子序列和。

【输入样例】

7
1 7 3 5 9 4 8

【输出样例】

18

【核心思想】

  1. 问题分析:给定长度为 \(N\) 的序列,求严格递增子序列的元素和的最大值。与经典 LIS 问题类似,但目标是最大化子序列的和而非长度。这是一个线性动态规划问题。

  2. 算法选择

    • 线性 DP\(f[i]\) 表示以第 \(i\) 个元素结尾的严格递增子序列的最大和
    • 双重循环:对于每个 \(i\),枚举前面所有可能的前驱 \(j\)
  3. 关键步骤

    • 状态定义\(f[i]\) 表示以 \(a[i]\) 结尾的严格递增子序列的最大和
    • 初始化\(f[i] = a[i]\),每个元素自身构成和为 \(a[i]\) 的子序列
    • 状态转移
      • 枚举 \(j\)\(1\)\(i-1\)
      • 如果 \(a[j] < a[i]\),则 \(f[i] = \max(f[i], f[j] + a[i])\)
    • 结果\(\max(f[i])\),遍历所有位置取最大值
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N^2)\),双重循环
    • 空间复杂度:\(O(N)\),一维 DP 数组
  5. 最大上升子序列和(MSIS)的核心思想

    • LIS 问题的变种:将"最长"改为"最大和",状态转移时累加元素值而非计数
    • 状态定义\(f[i]\) 表示以 \(a[i]\) 结尾的递增子序列的最大和
    • 状态转移:需要同时满足递增条件(\(a[j] < a[i]\))和最大和条件
    • 最优子结构:当前最优解由子问题的最优解组合而来
    • 适用于带权值的递增子序列问题

【解题思路】

【算法标签】

线性DP-一维

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 1005;  // 定义常量 N,表示数组的最大长度
int n;               // n 表示数组的长度
int a[N];            // a[i] 表示数组的第 i 个元素
int f[N];            // f[i] 表示以第 i 个元素结尾的严格递增子序列的最大和

int main()
{
    // 输入数组的长度
    cin >> n;

    // 输入数组的元素
    for (int i = 1; i <= n; i++)
        cin >> a[i];

    // 动态规划求解
    for (int i = 1; i <= n; i++)
    {
        f[i] = a[i];  // 初始化以第 i 个元素结尾的严格递增子序列的最大和为 a[i]

        // 遍历前面的元素,更新 f[i]
        for (int j = 1; j < i; j++)
        {
            if (a[j] < a[i])  // 如果前面的元素小于当前元素
                f[i] = max(f[i], f[j] + a[i]);  // 更新 f[i]
        }
    }

    int res = 0;  // res 表示严格递增子序列的最大和

    // 遍历 f 数组,找到最大值
    for (int i = 1; i <= n; i++)
        res = max(res, f[i]);

    // 输出结果
    cout << res << endl;

    return 0;
}
// 引入所有标准库头文件,方便使用各种标准库功能
#include <bits/stdc++.h>

// 使用标准命名空间,避免每次使用标准库函数时都需要加 std:: 前缀
using namespace std;

// 定义常量
const int N = 1010;  // 数列长度最大为1000

// 定义全局变量
int a[N], f[N], n;  // a[N]: 存储输入的数列,f[N]: f[i]表示以a[i]作为结尾的上升子序列的最大和,n: 数列的长度

// 主函数,程序的入口点
int main()
{
    // 读取数列的长度 n
    cin >> n;

    // 循环读取数列的每个元素,并存储到数组 a[i] 中,索引从 1 开始
    for (int i = 1; i <= n; i++)
        cin >> a[i];  // 读入数列

    // =============================
    // 动态规划部分:计算以每个元素 a[i] 作为结尾的上升子序列的最大和
    // =============================
    for (int i = 1; i <= n; i++)  // 遍历每个元素 a[i] 作为结尾
    {
        // 初始化以 a[i] 作为结尾的上升子序列的最大和为 a[i] 自身(即子序列只包含 a[i])
        f[i] = a[i];

        // 遍历 a[i] 之前的所有元素 a[j],寻找可以接在 a[i] 前面的元素
        for (int j = 1; j < i; j++)
        {
            // 如果元素 a[j] 的值小于元素 a[i] 的值,表示可以形成上升子序列
            if (a[j] < a[i])  // a[j]、a[i] 是上升序列
                // 更新以 a[i] 作为结尾的上升子序列的最大和为 f[j] + a[i] 和当前 f[i] 中的较大值
                f[i] = max(f[i], f[j] + a[i]);
        }
    }

    // =============================
    // 计算并输出最大的上升子序列的和
    // =============================
    // 初始化结果变量 ans,用于记录最大的上升子序列的和
    int ans = 0;

    // 遍历所有元素,计算以每个元素 a[i] 作为结尾的上升子序列的最大和,并更新 ans
    for (int i = 1; i <= n; i++)
        // 更新 ans 为 f[i] 和当前 ans 中的较大值
        ans = max(ans, f[i]);

    // 输出最大的上升子序列的和
    cout << ans << endl;

    // 程序正常结束,返回 0
    return 0;
}

【运行结果】

7
1 7 3 5 9 4 8
18
posted @ 2026-06-15 09:45  团爸讲算法  阅读(3)  评论(0)    收藏  举报