目录
题目
题目描述
给出一个长度为N的整数序列,求出包含它的第K个元素的最长上升子序列。
输入
第一行两个整数N, K,第二行N个整数
输出
如题目所说的序列长度。
样例输入
8 6
65 158 170 299 300 155 207 389
样例输出
4
【数据范围】
0 < N ≤ 200000,0 < K ≤ N
解析
朴素LIS
可以看到这是一道dp的典型题目——LIS。
令dp[i]为以第i个元素结尾的最长的上升子序列的的长度。那么即有转移方程:。也就是说从1到 i - 1 枚举最长的长度来作为第i个的长度,但这里有个条件,那就是a[i] 必须要大于 a[j]。
具体不再赘述,实在有需要的可以自己再看下百度哈。
#include <cstdio>
#define MAXN 100
#define max(a, b) a > b ? a : b
int n, a[MAXN + 5], dp[MAXN + 5], ans;
int main (){
scanf ("%d", &n);
for (int i = 1; i <= n; i++){
scanf ("%d", &a[i]);
dp[i] = 1;
}
for (int i = 2; i <= n; i++){
for (int j = 1; j < i; j++){
if (a[i] > a[j])
dp[i] = max (dp[i], dp[j] + 1);
}
ans = max (ans, dp[i]);
}
printf ("%d\n", ans);
}
但是可以看到,这是个的时间复杂度,要是放在这里的话绝对超时,因此我们得需要用一个更快捷的方法。
lower_bound优化LIS
首先我们令dp[i]为长度为i的LIS的末尾元素。
首先我们想想可以知道,在多个长度相等的LIS中,肯定是末尾更小的LIS更有潜力,因此在长度相等的情况下,我们肯定会优先选择更小的元素作为末尾对吧。这也就成为了我们dp转移的重要根据。
也就是说,如果a[i] > dp[len],那么就直接dp[++len] = a[i]就可以了,也就是说把a[i]作为更长的LIS的末尾。如果说是小于的话,那么就必须在前面找到第一个比它大的dp,使它更新为a[i]。
有点蒙是不是,当初我也是这样的,但是举例实践了一下后就能明白了:
a[] = 1 2 4 5 4 6 3
先将ans[1]赋值为a[1],代表长度为1的LIS末尾为1。
i = 2,因为a[i]大于了ans[len],所以len++,代表长度为2的LIS末尾为2。
i = 3,因为也大于了,所以同上,不再赘述。
i = 4,同上。
i = 5,因为此时a[i] 小于了ans[len],找到第一个大于等于a[i]的下标,即为3,更新ans[3],代表长度为3的LIS末尾为3。
…………
最后输出len,最长的LIS。
在这里已经把思路讲完了,但是我们应该怎么用代码实现呢?可以看到dp其实是一个有序的上升的数组,所以我们可以用二分来找到第一个大于等于的地方。当然也可以用C++自带的函数:lower_bound。不会的自己去查下百度哈
#include <cstdio>
#include <algorithm>
#define MAXN 100000
using namespace std;
int n, a[MAXN + 5], ans[MAXN + 5];
int main (){
scanf ("%d", &n);
for (int i = 1; i <= n; ++i)
scanf ("%d", &a[i]);
int len = 1;
ans[len] = a[1];
for (int i = 2; i <= n; ++i){
if (a[i] > ans[len]){
ans[++len] = a[i];
}
else{
int pos = lower_bound (ans + 1, ans + 1 + len, a[i]) - ans;
ans[pos] = a[i];
}
}
printf ("%d\n",len);
}
包含第k个元素的LIS及优化
在这里就更复杂些,要把第k个元素也包含进去,那么前面的单纯lower_bound就不够用了。
“二分”求解
因为这个题目求的是包含k的LIS,所以我在看到题后就想到了一种很奇妙 魔性 方法:在k的前面找一次最长下降子序列,然后再在k的后面找一次最长上升子序列,最后再把两个序列的长度相加不就OK了吗?这样的复杂度也才为而已。
当然,在求上升的子序列时是比较好求的,只需要把上面的套进去即可。但是下降的呢?在这里我就被拦住了。本来好不容易想出来一种方法,但是却又 作死 找到了一种数据给pass掉。改来改去最后竟然只能水过样例??!!
在这里其实可以用一个小技巧——greater。(其实这个东西我以前就知道,只不过忘了而已,白白错了好多遍,幸亏旁边的救助)greater表示内置类型从大到小。当然具体我不想讲太多,大家可以到这上面搜一下。
演示一下具体用法:
#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;
int n, a[25];
int main (){
scanf ("%d", &n);
for (int i = 1; i <= n; i++)
scanf ("%d", &a[i]);
sort (a + 1, a + 1 + n, greater <int> ());
for (int i = 1; i <= n; i++)
printf ("%d ", a[i]);
}
输入:
5
1 5 4 6 7
输出:
7 6 5 4 1
这下能看出来了吧,本来sort是从小到大排序的,但是在这里它却是从大到小排序,可见一斑。所以在找最长下降子序列时就可以借助这个东西了。
#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;
#define MAXN 200000
int n, k, a[MAXN + 5], dp1[MAXN + 5], dp2[MAXN + 5], len1, len2;
int main (){
scanf ("%d%d", &n, &k);
for (int i = 1; i <= n; i++){
scanf ("%d", &a[i]);
}
dp1[++len1] = a[k];
dp2[++len2] = a[k];
for (int i = k; i <= n; i++){
if (a[i] > a[k]){
if (a[i] > dp1[len1])
dp1[++len1] = a[i];
else{
int pos = lower_bound (dp1 + 1, dp1 + 1 + len1, a[i]) - dp1;
dp1[pos] = a[i];
}
}
}
for (int i = k; i; i--){
if (a[i] < a[k]){
if (a[i] < dp2[len2])
dp2[++len2] = a[i];
else{
int pos = lower_bound (dp2 + 1, dp2 + 1 + len2, a[i], greater <int> ()) - dp2;
dp2[pos] = a[i];
}
}
}
printf ("%d\n", len1 + len2 - 1);
}
至于在这里为什么要减一个1呢?是因为dp1和dp2都有一个k,就会多加了一个长度。(应该不会有人问这个吧,就当我在自言自语)
从前至后依次求解
这个就比较 水 简单好想了。只需要 一个 两个预处理就行了,然后就可以规规矩矩的从前往后依次来求LIS,如果上面还有点疑问的童鞋这里就有福音了,可以说这个思路 是个人都能掌握 十分好掌握。当然这里附上一位的链接
#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;
#define MAXN 200000
int n, k, a[MAXN + 5], dp[MAXN + 5], len;
int main (){
scanf ("%d%d", &n, &k);
for (int i = 1; i <= n; i++){
scanf ("%d", &a[i]);
}
int i = 1;
while (i < k && a[i] >= a[k])
i ++;
if (i < k)
dp[++len] = a[i];
for (; i <= k; i++){
if (a[i] < a[k]){
if (a[i] > dp[len])
dp[++len] = a[i];
else{
int pos = lower_bound (dp + 1, dp + 1 + len, a[i]) - dp;
dp[pos] = a[i];
}
}
}
i = k + 1;
while (i <= n && a[i] <= a[k])
i ++;
if (i <= n)
dp[++len] = a[i];
for (; i <= n; i++){
if (a[i] > a[k]){
if (a[i] > dp[len])
dp[++len] = a[i];
else{
int pos = lower_bound (dp + 1, dp + 1 + len, a[i]) - dp;
dp[pos] = a[i];
}
}
}
printf ("%d\n", len + 1);
}