感觉题解好难写QAQ,左脑攻击右脑

问题描述

给出一个由 \(n(n\le 5000)\) 个不超过 \(10^6\) 的正整数组成的序列。请输出这个序列的最长上升子序列的长度。

最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是单调增大的。
(出自 洛谷B3637 最长上升子序列

朴素DP

分析

设正整数序列为 A,\(f_i\) 是以 \(i\) 为结尾的最长子序列的长度,则容易想到 \(f_i = 1 + max f_j(a_i > b_j, i>j)\)
下以 \(\{0,8,4,12,2\}\) 为例:

以 0 为结尾,0 之前没有数字,\(f_1 = 1\)
以 8 为结尾,向前遍历找到 \(f_1\)\(f_2 = 1 + f_1\)
以 4 为结尾,向前遍历找到 \(f_1\)\(f_3 = 1 + f_1\);
以 12 为结尾,向前遍历找到 \(f_2\)\(f_3\)\(f_3 = 1 + max\{f_2, f_3\}\);
以2 为结尾,向前遍历找到 \(f_1\)\(f_3 = 1 + f_1\);

最后对 f 进行一次遍历找到最大值,即为最长上升子序列的长度。

代码

#include<iostream>
#include<cmath>
using namespace std;

const int N = 5e3 + 10

int n;
int num[N], f[N];
int ans = 0;

int main() {
	cin >> n;
    for (int i = 1; i <= n; i++) f[i] = 1;
	for (int i = 1; i <= n; i++) {
        cin >> num[i];
        for (int j = 1; j <= i; j++) {
			if (a[i] > a[j])f[i] = max(f[i], f[j] + 1);
            ans = max(ans, f[i]);
        }
    }
    cout << ans << endl;
}

时间复杂度为 \(O(n^2)\),显然时间复杂度并不优秀,当数据范围增大到 \(1e5\) 时会TLE,所以接下来考虑对时间复杂度进行优化。

贪心优化

分析

设正整数序列为 A,\(f_i\) 是以 \(i\) 为结尾的最长子序列的长度,\(B_i\) 是对最长子序列的记录。
\(B_i\)\(B_{i + 1}\) 的关系有三种

\(a_{i+1}<b_{i_1}\rightarrow f_{i+1} = 0+1 = 1,b_{i+1_1}=a_{i+1}\)\(B_{i+1}\) 继承 \(B_i\)\(0\) 个数字
\(b_{i_{end}}<a_{i+1}\rightarrow f_{i+1} = end+1=f_{i}+1,b_{i+1_{end}}=a_{i+1}\)\(B_{i+1}\) 继承 \(B_i\)\(end\) 个数字,即全部数字
\(b_{i_{j-1}}<a_{i+1}<b_{i_{j}}\rightarrow f_{i+1} = (j-1) + 1,b_{i+1_j}= a_{i+1}\)\(B_{i+1}\)继承 \(B_i\)\(j-1\) 个数字

通俗来讲,即在 B 找到第一个不小于\(a_{i+1}\) 的数的下标 \(x\),令 $b_x =a_{i+1} \(,\)f_{i+1}= x$。

\(a_i\) 插入 b_x 位置,即\(b_{x-1} < b_x < a_i < b_{x + 1}\),以\(a_j\)为结尾的最长子序列有三种可能:

\(a_j = a_i \rightarrow B\{b_1, b_2, \dots,b_{x-1},a_j\}\)
\(a_j>a_i\rightarrow B\{b_1,b_2,\dots,a_i,b_{x+1},\dots,a_j\}\)
\(a_j<a_i\rightarrow B\{b_1, b_2,\dots,a_j\}\)

代码实现

#include<iostream>
#include<algorithm>
#include<cmath>

using namespace std;

const int N = 1e5 + 10;
const int mod = 998244353;
map<int, int> mp;
int n;
int a[N], b[N];
int res[N];

int f[N];
int idx;
int main() {
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= n; i++) res[i] = INF;
	for (int i = 1; i <= n; i++) {
		int p = lower_bound(res + 1, res + 1 + n, mp[a[i]]) - res;
		res[p] = a[i];
		if (p > idx) idx = p;
	}
	cout << idx << endl;
}

最优解记录

分析

从之前的分析可以看出,以 \(a_j\) 为结尾的最长子序列其实就是第一层循环 \(i = j\) 时,res数组对应的前p个数。
利用 \(ans\) 记录一组最优解

对于 \(p < idx\) ,不是目标解,不处理
对于 \(p > idx\),将此时对应的 \(a_i\) 推入 \(ans\)
对于 \(p=idx\),判断 \(ans.back()\)\(a_i\) 的大小关系

#include<iostream>
#include<algorithm>
#include<cmath>

using namespace std;

const int N = 1e5 + 10;
int n;
int res[N];
vector<int> ans;
int idx;
int main() {
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= n; i++) res[i] = INF;
	for (int i = 1; i <= n; i++) {
		int p = lower_bound(res + 1, res + 1 + n, a[i]) - res;
		res[p] = a[i];
		if (p > idx) idx = p, ans.push_back(a[i]);
		else if(p == idx && ans.back()>a[i]) {
			ans.pop_back();
			ans.push_back(a[i]);
		}
	}
	cout << idx << endl;
	for (auto i: ans) cout << i <<" ";
}