动态规划
问题中“子序列”指的是从原序列中取出任意一部分元素,并保持其原来的顺序构成的序列,这些元素在原来的序列中不一定要全部连续。比如说序列{49,28,77,35,14},从中取出第1,2,4个元素按顺序构成{49,28,35},就是该序列的子序列。如果这个子序列中的元素从头端到尾端单调增加,那么就称它为“上升子序列”,现在需要求解的就是这些上升子序列中最长的长度。
我们可以将问题分解成更细微的部分,首先对于序列中的每一个单独元素,我们都把它当成长度为1的上升子序列。然后对于序列s上某个位置的元素s[i],如果在1∼i−1的区间上有一个元素s[j]满足s[j]<s[i],那么{s[j],s[i]}就可以构成长度为2的上升子序列。如果位置j还是某条长度为L的上升子序列的末尾,那么追加上s[i]后就可以构成以i为末尾的上升子序列。该子序列是否最长,都满足这个性质。
接下来,我们用dp(i)表示以i结尾的上升子序列的最大长度,可以根据上面的性质得出,当j<i时,有下面的递推式:
dp(i)={max{dp(i),dp(j)+1},s[i]>s[j]dp(i),s[i]≤s[j]
s[i]≤s[j]时,无法用j去更新i,dp(i)保持不变,s[i]>s[j]时,再取一个k<j,s[k]<s[j],假设dp(j)=x,dp(k)>(x+1),那么在遇上j位置之前,dp(i)就已经通过dp(k)更新完毕了,而不会再有dp(j)什么事。所以如果此时保证了j位置上的是最优解,那么i位置上也就是最优解了。
由上面的递推公式可得出,整个序列的最长上升子序列一定会以序列中某个位置i结束,这样的话其长度就是以每个位置为结尾的最长上升子序列长度之间的最大值。
C++ 代码
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
int* arr = new int[n],
* dp = new int[n];
for (int i = 0; i < n; i++) {
cin >> arr[i];
dp[i] = 1;
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (arr[j] < arr[i]) { //(j,i)能构成上升子序列,就按照递推公式去推
dp[i] = max(dp[i], dp[j] + 1);
}
}
}
cout << *max_element(dp, dp + n) << endl; //输出所有位置为末尾的情况下,最长上升子序列长度的最大值
return 0;
}