感觉题解好难写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 <<" ";
}