LIS 略解
这是一个非常经典的问题。
有两种解法,一种是 \(\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\),即长度最长的。
放一道经典例题。
第一问是求一个单调不增序列长度。
这个第二问需要的定理我不讲证明方法(绝不是因为我不会qwq。总之就是求一个单调增加的序列长度。
第二问是刚才原模原样的代码。第一问的话我们可以把数组倒过来,然后做一个单调不减的序列长度即可。
#include <bits/stdc++.h>
#define rep(i, a, b) for(register int i = a; i <= b; ++i)
using namespace std;
constexpr int N = 1e5 + 5;
int len, x, n, ans1, ans2, a[N], f[N];
int main() {
	while(cin >> x) {
		a[++n] = x;
	}
	rep(i, 1, n) {
		len = lower_bound(f + 1, f + ans2 + 1, a[i]) - f;
		f[len] = a[i];
		ans2 = max(ans2, len);
	}
	reverse(a + 1, a + n + 1);
	memset(f, 0, sizeof f);
	rep(i, 1, n) {
		len = lower_bound(f + 1, f + ans1 + 1, a[i]) - f;
		f[len] = a[i];
		ans1 = max(ans1, len);
	}
	cout << ans1 << '\n' << ans2;
	return 0;
}

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号