【动态规划】最长上升子序列

开头句:

雄关漫道真如铁,而今DP从头越。

 

没想到自己原来这么脆,连最初的最长上升子序列都不会。。。。QAQ

明明一年前学长就跟我们讲过了,自己也没多在意,后来比赛中无意中出现了,自己却没有能力实现解决。

真的很抱歉,只能临时套模板来解决。

 

好了,重新开始吧,不怕自己不会,只怕自己不想学。

参考博客:尊重原创,大家可以访问一下


引例:

什么是最长上升子序列? 就是给你一个序列,请你在其中求出一段不断严格上升的部分,它不一定要连续。

就像这样:2,3,4,7和2,3,4,6就是序列2 5 3 4 1 7 6的两种选取方案。最长的长度是4.

那么,怎么求出它的最大上升子序列长度为4呢?这里介绍两种方法,都是以动态规划为基础的。

首先,我们先介绍较慢(O(n2n2))的方法。我们记num为到这个数为止,最长上升子序列的长度。

这种方法就是每一次寻找“可以接下去的”,换句话说,设原序列为a,则

当   ajnumi时,numi=numj+1。

对于每一个数,他都是在“可以接下去”的中,从前面的最优值+1转移而来。

因此,这个算法是可以求出正确答案的。复杂度很明显,外层i枚举每个数,内层j枚举目前i的最优值,即O(n^2)


那么,有没有更快的方法呢?当然有。这回要用到二分

我们回想一下,在上面O(n^2)的程序中,哪些地方看起来比较费时?

没错,就是内层用于更新i的循环。因为每一次他都要查找一遍,效率并不高。

回到题目,我们发现,他只要我们求长度,所以?

我们可以模拟一个队列

所以每遇到一个比队尾元素大的数,就放进队伍后面里,遇到比队头元素大的就二分查找前边的元素,找到一个“最应该被换掉的元素”,用新数去更新前边的元素。

这个算法不难证明也是正确的。因为前面每一次的枚举都换成了二分,内层的复杂度从n降到了log2,外层不变。所以总的复杂度是O(nlog2n)


 

最朴素的最长上升子序列(LIS):

根据引例而写的,里面有必要的注释。

#include
using namespace std;
int main()
{
    int a[8]={0,2,5,3,4,1,7,6};
    int n=7;
    int dp[10]={0};
    dp[1]=1;
    int ans=0;
    for(int i=2;i<=n;i++){
        for(int j=i-1;j>=0;j--){
            if(a[j]

 

利用二分来实现最长上升子序列:

#include
using namespace std;
const int N=1e2;
const int MAX=0x3f3f3f3f,MIN=-0x3f3f3f3f;
int main()
{
    int a[N]={0,2,5,3,4,1,7,6},n=7;
    int dp[N];
    int top=1;
    dp[1]=a[1];
    //printf("%d %d\n",MAX,MIN);
    for(int i=2;i<=n;i++){
        if( a[i] < MIN   ||   a[i] > MAX ){ continue; } //判断数据的合法性

        if( a[i] < dp[1] ){       //找到在序列中最小值还小的值

            dp[1] = a[i];         //替换其最小值

        }else if( a[i] > dp[top]){//找到比序列中最大值还大的值

            dp[++top] = a[i];     //加入到序列中,并位于最后。

        }else{                    //找到序列 中等水平

            int pos=lower_bound(dp+1,dp+1+top,a[i])-dp;
                                  //找到对应的位置改变中等水平
            dp[pos]=a[i];         //将其替代
        }
    }
    printf("%d\n",top);           //LIS=该序列的长度
    return 0;
}

    /*
    for(int i=2;i<=n;i++){
        if( a[i] < MIN   ||   a[i] > MAX ){ continue; } //判断数据的合法性

        if( a[i] < dp[1] ){       //找到在序列中最小值还小的值

            dp[1] = a[i];         //替换其最小值

        }else if( a[i] >= dp[top]){//找到比序列中最大值大于等于的值

            dp[++top] = a[i];     //加入到序列中,并位于最后。

        }else{                    //找到序列 中等水平

            int pos=upper_bound(dp+1,dp+1+top,a[i])-dp;
                                  //找到对应的位置改变中等水平
            dp[pos]=a[i];         //将其替代
        }
    }
    printf("%d\n",top);           //LIS=该序列的长度
    */

 

最长上升子序列LIS ,O(N*log N)写成函数:

 

#include
using namespace std;
const int MAX=0x3f3f3f3f,MIN=-0x3f3f3f3f;
const int N=1e2;
void solve1(int a[],int n){
    int *dp;
    dp=(int *)malloc(sizeof(int)*(n+2));
    dp[1]=a[1];
    int top=1;
    for(int i=2;i<=n;i++){
        if( a[i]MAX ) continue;

        if( a[i] < dp[1] ) {
            dp[1]=a[i];
        }else if( dp[top] < a[i] ){
            dp[++top]=a[i];
        }else{
            int pos=lower_bound(dp+1,dp+1+top,a[i])-dp;
            dp[pos]=a[i];
        }
    }
    printf("%d\n",top);
    for(int i=1;i<=top;i++){
        printf("%d%c",dp[i],i==top?'\n':' ');
    }
}
void solve2(int a[],int n){
    int *dp;
    dp=(int *)malloc(sizeof(int)*(n+2));
    dp[1]=a[1];
    int top=1;
    for(int i=2;i<=n;i++){
        if( a[i]MAX ) continue;

        if( a[i] < dp[1] ) {
            dp[1]=a[i];
        }else if( dp[top] <= a[i] ){
            dp[++top]=a[i];
        }else{
            int pos=upper_bound(dp+1,dp+1+top,a[i])-dp;
            dp[pos]=a[i];
        }
    }
    printf("%d\n",top);
    for(int i=1;i<=top;i++){
        printf("%d%c",dp[i],i==top?'\n':' ');
    }
}
int main()
{
    int a[N]={0,2,5,3,4,1,7,7,6};
    int b[N]={0,2,5,3,3,4,1,7,6};
    int n=8;
    printf("严格递增子序列:\n");
    solve1(a,n);
    solve1(b,n);
    printf("不递减上升子序列:\n");
    solve2(a,n);
    solve2(b,n);
    return 0;
}

 

 

你可能感兴趣的:(dp,技巧型算法类)