题解: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
【核心思想】
-
问题分析:给定长度为 \(N\) 的序列,求严格递增子序列的元素和的最大值。与经典 LIS 问题类似,但目标是最大化子序列的和而非长度。这是一个线性动态规划问题。
-
算法选择:
- 线性 DP:\(f[i]\) 表示以第 \(i\) 个元素结尾的严格递增子序列的最大和
- 双重循环:对于每个 \(i\),枚举前面所有可能的前驱 \(j\)
-
关键步骤:
- 状态定义:\(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])\),遍历所有位置取最大值
-
时间/空间复杂度:
- 时间复杂度:\(O(N^2)\),双重循环
- 空间复杂度:\(O(N)\),一维 DP 数组
-
最大上升子序列和(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
浙公网安备 33010602011771号