Openjudge 1759:最长上升子序列
dp 做法
我们设 \(f_i\) 表示以第 \(i\) 格为结尾得最长上升子序列的长度。先来看样例。
数组 \(a\):1 7 3 5 9 4 8
数组 \(f\):1 2 2 3 4 3 4
我们枚举 \(i\),然后看 \(i\) 之前的第 \(j\) 位 (\(j \le i\)),判断 \(a_j\) 是否小于 \(a_i\),然后就有转移方程
初始化:\(f_i = 1\)。因为每个元素自己就是一个序列,所以初始化是 \(1\)。
代码
#include<iostream>
using namespace std;
int n, a[1005], f[1005];
int ans;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++) {
f[i] = 1;
for (int j = 1; j <= i; j++) { // 枚举 j
if (a[j] < a[i])
f[i] = max(f[i], f[j] + 1); // 状态转移方程
ans = max(ans, f[i]); // 取最大值
}
}
cout << ans;
return 0;
}
O(nlogn)
在 \(n \ge 10^5\) 的时候,dp 的做法就很危险。于是,我们就有了 \(O(nlogn)\) 的做法。
更改 \(f\) 数组的定义。设 \(f_i\) 表示长度为 \(i\) 的最长上升子序列的最小的结尾数字。
来看前面的样例。
数组 \(a\):1 7 3 5 9 4 8
\(i = 1\):\(f\) 数组加入元素 1。
\(i = 2\):\(7 > 1\),将 7 加入 \(f\)。
\(i = 3\):\(3 < 7\),将 7 替换成 3。
\(i = 4\):\(5 > 3\),将 5 加入 \(f\)。
\(i = 5\):\(9 > 5\),将 9 加入 \(f\)。
\(i = 6\):\(4 < 9\),将 \(f\) 中的 5 替换成 4。注意,这里不会因为数组顺序而改变结果,因为我们改变的是第一个大于当前这个数的位置。如果当前这个数是 \(a\) 中的最后一位,那么答案就是当前数组的长度,如果不是最后一位,那么后面还有数字,可以替换掉 \(f\) 数组当前的最后一位,如果没有能替换掉 \(f\) 的数字,那么 \(f\) 的最大长度没有被变化过,答案依旧保持不变。
\(i = 7\):\(8 < 9\),将 \(f\) 中的 9 替换成 8。
最终,\(f=\{1,3,4,8\}\)
根据以上的推测,我们得出了求出 \(f\) 的方法。
如果当前数字大于 \(f\) 的最后一个数字,那么直接接上,否则就替换在 \(f\) 中第一个大于当前这个数的位置,原理就是让越后面的数字越小,使得能接的数越多。
要找到第一个大于当前这个数的位置,我们可以使用 C++ 的upper_bound()来计算。不懂upper_bound()的可以看一下这个。
最终答案就是 \(f\) 数组的长度。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n, a[5003], s[5003]; // s[i] 表示以 i 为长度所结尾的最长上升子序列的末尾元素
int ans, cntp = 1;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
s[1] = a[1]; // 初始化
for (int i = 2; i <= n; i++) {
if (a[i] > s[cntp]) s[++cntp] = a[i]; // 如果大于最长上升子序列的末尾元素 那么直接接在末尾就好
else *upper_bound(s + 1, s + cntp + 1, a[i]) = a[i]; // 否则替换第一个大于 a[i] 的数
// 使得前面的元素较小,这样后面就能接更多的元素
}
cout << cntp;
return 0;
}
整篇文章讲的是针对严格上升的最长上升子序列,即不存在大于等于,但具体要求还是要看每一个题目的要求,需要随机应变。

浙公网安备 33010602011771号