【题目描述】
一个数的序列bi,当b1<b2<...<bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1,a2,...,aN),我们可以得到一些上升的子序列(ai1,ai2,...,aiK),这里1≤i1<i2<...<iK≤N1。比如,对于序列(1,7,3,5,9,4,8),有它的一些上升子序列,如(1,7),(3,4,8)等等。这些子序列中最长的长度是4,比如子序列(1,3,5,8)。
你的任务,就是对于给定的序列,求出最长上升子序列的长度。
【输入】
输入的第一行是序列的长度N(1≤N≤1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000。
【输出】
最长上升子序列的长度。
【输入样例】
7
1 7 3 5 9 4 8
【输出样例】
4
解题思路:
使用动态规划来求解最长上升子序列(Longest Increasing Subsequence,简称 LIS)问题。以下是关于该算法的状态设计、策略和状态转移方程的解释:
状态设计
- 状态定义:
dp[i]
表示以序列中第i
个元素结尾的最长上升子序列的长度。
策略
- 初始化:每个元素本身都构成一个长度为 1 的上升子序列,因此
dp[i]
初始化为 1。 - 状态转移:对于序列中的每个元素
a[i]
,在它之前的所有元素a[j] (j < i)
中找到比a[i]
小的元素,并尝试用a[i]
来扩展以a[j]
结尾的上升子序列。
状态转移方程
-
如果
a[i] > a[j]
(即当前元素比之前的某个元素大),则可以将a[i]
添加到以a[j]
结尾的上升子序列中,从而形成一个更长的上升子序列。因此,状态转移方程为:dp[i] = max(dp[i], dp[j] + 1) 对于所有 j < i 且 a[j] < a[i]
这个方程意味着
dp[i]
的值要么保持不变(即没有找到更长的上升子序列),要么更新为dp[j] + 1
(找到了一个以a[j]
结尾的更短的上升子序列,可以用a[i]
来扩展它)。 -
最终结果:最长上升子序列的长度是整个
dp
数组中的最大值,因为最长上升子序列不一定以序列中的最后一个元素结尾。
整个算法的时间复杂度是 O(n^2),其中 n 是序列的长度。这是因为对于每个元素 a[i]
,都需要检查它之前的所有元素 a[j]
来找到可能的上升子序列。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int n;
cin >> n; // 读取序列长度N
vector<int> a(n); // 定义一个整型vector a,大小为N,用于存储输入的序列
for (int i = 0; i < n; i++) {
cin >> a[i]; // 循环读取序列中的N个整数,存储到vector a中
}
vector<int> dp(n, 1); // 定义一个整型vector dp,大小为N,并全部初始化为1,dp[i]将用于存储以a[i]为结尾的最长上升子序列的长度
int ans = 1; // 初始化最长上升子序列的长度为1,因为至少每个数字本身构成一个长度为1的上升子序列
// 使用动态规划来求解最长上升子序列问题
for (int i = 1; i < n; i++) { // 遍历序列,从第二个数开始,因为每个数至少自己构成一个子序列
for (int j = 0; j < i; j++) { // 对当前数i之前的所有数进行遍历
if (a[i] > a[j]) { // 如果发现a[i]比a[j]大,则说明可以将a[i]加入到以a[j]结尾的上升子序列中,从而得到一个更长的上升子序列
dp[i] = max(dp[i], dp[j] + 1); // 更新dp[i]的值为dp[j]加1与dp[i]的当前值的较大者
}
}
ans = max(ans, dp[i]); // 更新最长上升子序列的长度
}
cout << ans << endl; // 输出最长上升子序列的长度
return 0;
}