这是一个非常经典的问题。
有两种解法,一种是 \(\mathcal O(n ^ 2)\) 的动态规划做法,一种是 \(\mathcal O(n \log n)\) 的贪心做法。
- 动态规划做法
设 \(dp_i\) 为以第 \(i\) 个数字结尾的最长单调增加序列。
然后枚举每个 \(j\) 使得 \(j < i\)。如果发现 \(a_j < a_i\) 就可以转移,\(dp_i = dp_j + 1\)。
如果求单调不减序列,把 \(<\) 换成 \(\leq\) 即可。
复杂度 \(\mathcal O(n ^ 2)\)。
代码自己写罢(
- 贪心做法
重心还是放在这个做法上。
想象有很多不同长度的单调增加序列,我们现在有一个新元素,那么就是要把它接在之前的单调增加序列上。
我们设 \(f_i\) 为长度为 \(i\) 的单调增加序列最后一个数字的最小值。
这样的话,由于我们已经取了最小值,\(f_i\) 代表的肯定是长度 \(i\) 最容易接上的一个子序列了。
这个时候证明一下 \(f_i\) 有单调不减的性质。
反证法,就是如果有一个长度比当前单调增加序列长,而且最小值还要更小的单调增加序列,那肯定是取不到当前的单调增加序列的,就会取这个单调增加序列的子序列了。
好的!最重要的说完啦!
接下来是具体实现。
贪心的想法是,我们要把目前这个元素接在它能接的最长单调增加序列上。
所以直接二分,找长度最长的,还允许新元素接上的 \(f_i\) 即可。
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 1e5 + 5;
int len, n, ans, a[N], f[N];
int main() {cin >> n;for(int i = 1; i <= n; ++i) cin >> a[i];for(int i = 1; i <= n; ++i) {len = lower_bound(f + 1, f + ans + 1, a[i]) - f;f[len] = a[i];ans = max(ans, len);}cout << ans;return 0;
}
如果想要求最长的单调不增序列,换成 upper_bound 就行。你可以想象一下,换成 upper_bound 之后,当有多个 \(f_i\) 相同时,他就会选择 \(i\) 最大的 \(f_i\),即长度最长的。