最长上升子序列II

题目描述

给出一个长度为N的整数序列,求出包含它的第K个元素的最长上升子序列。

输入

第一行两个整数N, K
第二行N个整数

输出

如题目所说的序列长度。

样例输入

8 6
65 158 170 299 300 155 207 389

样例输出

4

提示

【数据范围】

0 < N ≤ 200000,0 < K ≤ N

分析

这道题由于数据规模比较大,所以很容易超时
其实最容易想到的是以题目要求的点,找出它以前的最长上升子序列长度和它后面的最长上升子序列长度,再相加,再加上它本身的长度1,但是血淋淋的教训告诉我们,会超时。

那么这道题就要换个思路。
输入之后,先遍历一遍数组(原数组为a数组)。如果a[i]在a[k]的前面而且比a[k]大或者是在a[k]的后面而且比a[k]小(当然不取等,题目要求包含a[k],怎么可以去掉它呢),就可以删除它(就是continue掉,不处理它)。
我们用辅助数组f[i]表示长度为i的子序列的末尾的最小值,那么f数组最后一个的有值的元素的下标就是本题的答案。(当然f数组的值不是方案)(f[0]的初值为极小值,因为要保证第一个数大于f数组的最后一个数(即f[0]),其它为0)
是不是有点懵,没关系,我们来慢慢分析。先来解释一下这个f数组。每次访问一个数时,如果这个数比f数组的最后一个元素大,那么就将这个数加入f数组,否则,就在f数组当中找一个元素,使这个数小于这个元素且大于这个元素的前一个元素,然后替换掉它。
举个例子吧(样例太渣,自己出的一组数据)
8 6
12 4 57 36 89 103 124 5

a[k]=103
①12 4 57 36 89 103 124 5
@@↑
12<103,所以不跳过
12>f[0],所以f[1]=12(12自己组成一个长度为1的上升子序列)
②12 4 57 36 89 103 124 5
@@@@↑
4 < 103,所以不跳过
4 < f[1],所以在f数组中查找元素
f[0] < 4 < f[1],所以4替换12,f[1]=4(4自己组成一个长度为1的上升子序列,且比12更优)
③ 12 4 57 36 89 103 124 5
@@@@@@@@↑
57 < 103,所以不跳过
57 > f[1],所以将57加入数组,f[2]=57
④12 4 57 36 89 103 124 5
@@@@@@@@@@@↑
36 < 103,所以不跳过
36 < f[2],所以在f数组中查找元素
f[1] < 36 < f[2],所以36替换57,f[2]=36(4或12与36组成一个长度为2的上升子序列,且比57更优)
⑤12 4 57 36 89 103 124 5
@@@@@@@@@@@@@@↑
89<103,所以不跳过
89>f[2],所以将89加入数组,f[3]=89
⑥12 4 57 36 89 103 124 5
@@@@@@@@@@@@@@@@@@↑
103=103,所以不跳过
103>f[3],所以将103加入数组,f[4]=103
⑦12 4 57 36 89 103 124 5
@@@@@@@@@@@@@@@@@@@@@@↑
124>103,所以不跳过
124>f[4],所以将124加入数组,f[5]=124
⑧12 4 57 36 89 103 124 5
@@@@@@@@@@@@@@@@@@@@@@@@↑
5<103,所以跳过

f数组最后一个的有值的元素的下标为5,所以答案就是5
为什么要进行替换呢?其原因就是为了保证解是最优解,保证f数组中的满足条件的值最小的话,就可以让更多的数大于f数组中的数,尽可能多得让他们加入f数组。
而大于就加入,保证了上升;第一个筛选保证序列是含有a[k]的,所以只要f[i]有值(f数组除了f[0],其它都是0),就代表有长度为i的且含a[k]的上升子序列,要求最长,当然也就是f数组最后一个的有值的元素的下标的值。
查找a[i]该在f数组的哪里替换的时候,要用二分查找。或者有两个函数可以进行查找:
lower_bound(起始地址,结束地址,给定元素)
返回第一个大于等于给定元素的地址
类比一下还有upper_bound(起始地址,结束地址,给定元素)
返回第一个小于等于给定元素的地址
条件:数组要有序
两个函数都在algorithm头文件下

代码实现如下:

#include
#include
using namespace std;
int n,s,k,a[200005],f[200005],l,r,m,t;
//f[i]表示长度为i的子序列的末尾的最小值
int main()
{
    scanf("%d %d",&n,&k);
    for(int i=1;i<=n;i++) 
        scanf("%d",&a[i]);
    f[0]=INT_MIN;
    for(int i=1;i<=n;i++)
    {
        if((i=a[k])||(i>k&&a[i]<=a[k])) 
            continue;
        if(a[i]>f[s]) 
        {
            s++;
            f[s]=a[i];
            continue;
        }
        l=0,r=s,t=0;
        while(l<=r)
        {
            int m=(l+r)/2;
            if(a[i]>f[m]) 
            {
                l=m+1;
                t=m;
            }
            else
                r=m-1;
        }
        if(f[t+1]>a[i])
            f[t+1]=a[i];//使f[i]的值尽量小
    }//用a[i]中满足条件的值替换f对应的值
    printf("%d\n",s);
}

你可能感兴趣的:(DP-线性dp-区间dp)