DP经典--百练2757--最长上升子序列

题意描述

有三种解法 n^2 和两个n*log(n)         一个讲得很好的博客:地址

n^2的,动态规划:

动归问题,首先很容易认为这道题的子问题就是,求前n个数字序列的解,这样的错误之处在于,“状态具有后效性”,前n个数字序列的解可能有多种,每一种可能结尾数字不一样,这样对后面的解就有了影响(后效性)

比较好的子问题是,求以第n个数为结尾的数字序列的解,这样的子问题,便没有了后效性

事实上,我认为第一种子问题如果加以处理也能消除后效性,但是我思考了一下觉得这样子会很麻烦,有兴趣的同学可以试试,一起讨论一下~

确定了子问题剩下的就很简单

ac代码:

#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;

#define maxn 1000+10
#define inf 1<<31

int dp[maxn],num[maxn];

int main(){
    int n,ans=-1;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>num[i];dp[i]=1;
        for(int j=1;j<i;j++){
            if(num[j]<num[i]){
            dp[i]=max(dp[i],dp[j]+1);
            }
        }
    }
    cout<<*max_element(dp+1,dp+n+1);
    return 0;
} 

n*log(n)的,贪心+二分:

新建一个low数组,low[i]表示长度为i的LIS结尾元素的最小值。对于一个上升子序列,显然其结尾元素越小,越有利于在后面接其他的元素,也就越可能变得更长。因此,我们只需要维护low数组,对于每一个a[i],如果a[i] > low[当前最长的LIS长度],就把a[i]接到当前最长的LIS后面,即low[++当前最长的LIS长度]=a[i]。 
那么,怎么维护low数组呢? 
对于每一个a[i],如果a[i]能接到LIS后面,就接上去;否则,就用a[i]取更新low数组。具体方法是,在low数组中找到第一个大于等于a[i]的元素low[j],用a[i]去更新low[j]。如果从头到尾扫一遍low数组的话,时间复杂度仍是O(n^2)。我们注意到low数组内部一定是单调不降的,所有我们可以二分low数组,找出第一个大于等于a[i]的元素。二分一次low数组的时间复杂度的O(lgn),所以总的时间复杂度是O(nlogn)。

ac代码:

#include<cstdio>
#include<algorithm>

#define maxn 1000+10

int lower[maxn],num[maxn]; 

int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&num[i]);
    }
    int ans=1;
    lower[1]=num[1];
    for(int j=2;j<=n;j++){
     if(num[j]>lower[ans]){
         lower[++ans]=num[j];
     }
     else{
         int l=std::lower_bound(lower+1,lower+ans,num[j])-lower;
         lower[l]=num[j];
     }
    }
    printf("%d
",ans);
    return 0;
}

最后这种方法看起来比较麻烦,用到了树状数组,我还没有学到,篮桥北貌似也不考这个,因此以后在更吧~

柳暗花明又一村
原文地址:https://www.cnblogs.com/ucandoit/p/8496086.html