Openjudge 1759:最长上升子序列

dp 做法

我们设 f i f_i fi 表示以第 i i i 格为结尾得最长上升子序列的长度。先来看样例。

数组 a a a1 7 3 5 9 4 8

数组 f f f1 2 2 3 4 3 4

我们枚举 i i i,然后看 i i i 之前的第 j j j 位 ( j ≤ i j \le i ji),判断 a j a_j aj 是否小于 a i a_i ai,然后就有转移方程

f i = max ⁡ ( f i , f j + 1 ) f_i = \max(f_i, f_j + 1) fi=max(fi,fj+1)

初始化: f i = 1 f_i = 1 fi=1。因为每个元素自己就是一个序列,所以初始化是 1 1 1

代码

#include
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 ≥ 1 0 5 n \ge 10^5 n105 的时候,dp 的做法就很危险。于是,我们就有了 O ( n l o g n ) O(nlogn) O(nlogn) 的做法。

更改 f f f 数组的定义。设 f i f_i fi 表示长度为 i i i 的最长上升子序列的最小的结尾数字。

来看前面的样例。

数组 a a a1 7 3 5 9 4 8

i = 1 i = 1 i=1 f f f 数组加入元素 1

i = 2 i = 2 i=2 7 > 1 7 > 1 7>1,将 7 加入 f f f

i = 3 i = 3 i=3 3 < 7 3 < 7 3<7,将 7 替换成 3

i = 4 i = 4 i=4 5 > 3 5 > 3 5>3,将 5 加入 f f f

i = 5 i = 5 i=5 9 > 5 9 > 5 9>5,将 9 加入 f f f

i = 6 i = 6 i=6 4 < 9 4 < 9 4<9,将 f f f 中的 5 替换成 4。注意,这里不会因为数组顺序而改变结果,因为我们改变的是第一个大于当前这个数的位置。如果当前这个数是 a a a 中的最后一位,那么答案就是当前数组的长度,如果不是最后一位,那么后面还有数字,可以替换掉 f f f 数组当前的最后一位,如果没有能替换掉 f f f 的数字,那么 f f f最大长度没有被变化过,答案依旧保持不变。

i = 7 i = 7 i=7 8 < 9 8 < 9 8<9,将 f f f 中的 9 替换成 8

最终, f = { 1 , 3 , 4 , 8 } f=\{1,3,4,8\} f={1,3,4,8}

根据以上的推测,我们得出了求出 f f f 的方法。

如果当前数字大于 f f f 的最后一个数字,那么直接接上,否则就替换在 f f f 中第一个大于当前这个数的位置,原理就是让越后面的数字越小,使得能接的数越多。

要找到第一个大于当前这个数的位置,我们可以使用 C++ 的upper_bound()来计算。不懂upper_bound()的可以看一下这个。

最终答案就是 f f f 数组的长度。

代码

#include
#include
#include
#include

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;
}

整篇文章讲的是针对严格上升的最长上升子序列,即不存在大于等于,但具体要求还是要看每一个题目的要求,需要随机应变。

你可能感兴趣的:(Openjudge,c++,算法)