题解:AcWing 895 最长上升子序列
【题目来源】
AcWing:895 最长上升子序列 - AcWing题库
【题目描述】
给定一个长度为 \(N\) 的数列,求数值严格单调递增的子序列的长度最长是多少。
【输入】
第一行包含整数 \(N\)。
第二行包含 \(N\) 个整数,表示完整序列。
【输出】
输出一个整数,表示最大长度。
【输入样例】
7
3 1 2 1 8 5 6
【输出样例】
4
【核心思想】
-
问题分析:给定长度为 \(N\) 的数列,求数值严格单调递增的子序列的最大长度。这是一个经典的线性动态规划问题,需要找到以每个元素结尾的最长上升子序列。
-
算法选择:
- 线性 DP:\(f[i]\) 表示以 \(a[i]\) 结尾的最长上升子序列的长度
- 暴力解法:对于每个位置 \(i\),枚举前面所有位置 \(j < i\),若 \(a[j] < a[i]\),则可以接在 \(a[i]\) 后面
-
关键步骤:
- 初始化:\(f[i] = 1\),每个元素自身构成长度为 \(1\) 的子序列
- 状态转移:对于每个 \(i\),枚举 \(j\) 从 \(1\) 到 \(i-1\):
- 若 \(a[j] < a[i]\),则 \(f[i] = \max(f[i], f[j] + 1)\)
- 答案:\(\max(f[1], f[2], \ldots, f[N])\),取所有位置的最大值
-
时间/空间复杂度:
- 时间复杂度:\(O(N^2)\),双重循环枚举
- 空间复杂度:\(O(N)\),一维 DP 数组
-
最长上升子序列(LIS)的核心思想:
- 状态定义:\(f[i]\) 表示以第 \(i\) 个元素结尾的 LIS 长度
- 状态转移:对于每个 \(i\),找前面所有比 \(a[i]\) 小的元素 \(j\),取 \(f[j]\) 的最大值加 \(1\)
- 最优子结构:以 \(a[i]\) 结尾的 LIS 可以由前面某个 \(a[j]\) 结尾的 LIS 扩展而来
- 优化方向:可以使用二分优化至 \(O(N \log N)\),用数组维护每个长度的最小结尾元素
- 适用于序列分析、数据比较等场景
【算法标签】
线性DP-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 1005; // 定义常量N,表示数组的最大长度
int n; // 输入的数组长度
int a[N]; // 存储输入的数组
int f[N]; // f[i]表示以a[i]结尾的最长上升子序列的长度
int main()
{
cin >> n; // 输入数组的长度
for (int i = 1; i <= n; i++) cin >> a[i]; // 输入数组元素
// 动态规划求解最长上升子序列
for (int i = 1; i <= n; i++)
{
f[i] = 1; // 初始化f[i],至少包含a[i]一个数,所以长度为1
// 遍历a[i]之前的所有元素,寻找可以接在a[i]前面的最长上升子序列
for (int j = 1; j < i; j++)
if (a[j] < a[i]) // 如果a[j] < a[i],说明a[j]可以接在a[i]前面
f[i] = max(f[i], f[j] + 1); // 更新f[i],取f[j]+1和当前f[i]的最大值
}
int res = 0; // 用于存储最终的最长上升子序列的长度
for (int i = 1; i <= n; i++)
res = max(res, f[i]); // 遍历f数组,找到最大值
cout << res << endl; // 输出结果
return 0;
}
【运行结果】
7
3 1 2 1 8 5 6
4
浙公网安备 33010602011771号