最长上升子序列(LIS)

题目描述:

  传送门

 

题解思路:和最长公共子序列一样,LIS也是一个经典的dp问题。动规的常见套路是将原问题化简成多个形式相同的子问题。这里求序列的最长上升子序列长度,首先求以ai(ai是序列中的元素,1<=i<=n)结尾的最长上升子序列长度,再从这些以ai结尾的最长子序列长度中选择一个最长的作为整个序列的最长上升子序列长度。

核心算法:

假设maxlen(i)表示以ai为终点的最长上升子序列长度,则有:

初始状态maxlen(1)=1(因为以a1为终点的上升序列就只有a1自己)

maxlen(i)=max{maxlen(j): 1<=j<i  且  aj<ai  且 i≠1} + 1

解释:即在以ai为终点的上升子序列中,首先找出以aj(1<=j<i)为终点的最长上升子序列长度(逐个比较找最长),并且i不为1,且aj的值要小于ai,找出最长的上升子序列长度加1则是以ai为终点的上升子序列长度了(加1是因为算上ai序列长度加1,并且此处ai的值是要大于aj的)

 

#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
#define re register

int main(){
    int a[100005];
    int maxlen[100005];
    int n=0;
    cin>>n;
    
    for(re int i=1;i<=n;i++){
        cin>>a[i];
    }
    
    for(re int i=1;i<=n;i++){        //注意maxlen数组要赋初值为1 
        maxlen[i]=1;
    }
    for(re int i=2;i<=n;i++){        //以a[i]点为终点 
        for(re int j=1;j<i;j++){        //把a[i]前面的点全都看一遍,并且进行比较 即分别以a[j]作为上升终点再去比较序列长度
            if(a[j]<a[i]){        //进行更新上升长度maxlen[i],如果前面的点a[j]的值小于a[i],否则保存不变 
                maxlen[i]=max(maxlen[i],maxlen[j]+1);//此处加1因为a[j]<a[i] 
            }
        }
    }
    
    //由于排后面的点a[i]不一定比排前面的点a[j]大,因此要在maxlen数组里面搜索最大的元素
    //而不是想当然地认为maxlen[n]就是最大的 
    cout<<*max_element(maxlen+1,maxlen+1+n);        //输出最大值 
    return 0;
}

很容易判断上面的算法是O(n^2)的,当数据规模比较大可能会出现TLE,下面将进行O(nlogn)的算法改进。

 

 

定义maxlen[k]:长度为k的上升子序列的最末元素,若有多个长度为k的上升子序列,则记录最小的那个最末元素。

maxlen数组是专门用于存放不同长度的上升子序列的最后一个元素,并且如果长度都为k的多种上升子序列,那么maxlen[k]存放的是最小的上升序列末尾元素。注意maxlen数组存放的不是上升子序列。

 

我们可以明确maxlen数组中元素是单调递增的,下面要用到这个性质。
1.首先len = 1,maxlen[1] = a[1](因为元素长度k为1)

2.对a[i]:

若a[i]>maxlen[len],那么len++,maxlen[len] = a[i];(此时最长的长由于度len加1)

例如maxlen中现在元素为 2 4 5 6(maxlen[1]=2  maxlen[4]=6 此时len=4) 现在来了一个元素a[i]假设为8 而8>maxlen[4],因此len++变为5 maxlen[5]=8

但是注意这里的2 4 5 6 8不是原序列的最长上升子序列(只是记录了不同长度序列最末一个元素的数组)


否则,我们要从maxlen[1]到maxlen[len-1]中找到一个j,满足d[j-1]<a[i]<d[j],则根据d的定义,我们需要更新长度为j的上升子序列的最末元素(使之为最小的)即 maxlen[j] = a[i];

例如maxlen中元素为2 4 5 6(maxlen[1]=2  maxlen[4]=6 此时len=4)现在来了一个元素a[i]为3,由于3<maxlen[len],因此len不变,找到2<3<4,所以maxlen[2]=3(也就是4被替换了)变成了2 3 5 6.因此序列长度为2的上升子序列最末的元素从4变成了3,但是它的序列长度没改变任然为2,而且len也没有改变。


a[i]数组全部遍历完后最终答案就是len,即是最长上升子序列的长度。


利用maxlen的单调性,在查找j的时候可以二分查找,从而时间复杂度为nlogn。

 

#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
#define re register

int main(){
    int a[100005];
    int maxlen[100005]={0};
    int len=0;
    int n=0;
    cin>>n;
    
    for(re int i=1;i<=n;i++){
        cin>>a[i];
    }
    
    maxlen[1]=a[1];        //第一个数直接放入maxlen中
    len=1;
    for(re int i=2;i<=n;i++){        //从第2个数开始搜起 ;
        if(maxlen[len]<a[i]){        //maxlen[len]是当前maxlen数组最末的一个数 
            len++;
            maxlen[len]=a[i];
        }else{
            int *pos;
            pos=lower_bound(maxlen+1,maxlen+1+len,a[i]);    //返回的是地址 
            *pos=a[i];                //加1是因为下标从1开始 
        }
    }
    cout<<len;
    return 0;
}

 

posted @ 2020-05-11 11:24  neverstopcoding  阅读(390)  评论(0编辑  收藏  举报