zoukankan      html  css  js  c++  java
  • 最长不下降子序列 从O(n)到O(nlogn)

    给定一个序列,求其最长不下降子序列的长度

    例如{3,1,5,2,4,2,3,7,9},其最长不下降子序列为{1,2,2,3,7,9},长度为6

    我们用a[i]存储给定序列

    先观察最长不下降子序列的形成

    假设前i个数存在若干个不下降子序列,我们已找出其中长度为len的最长不下降子序列,对于a[i+1],可以选择将它接在某个不下降子序列的末尾使该不下降子序列的长度增加1,或者让它成为一个新的长度为1的不下降子序列。a[i+1]接在哪个不下降子序列后面可能会影响前i+1个数中最长不下降子序列的长度,但是无论对a[i+1]怎样处理,都不会影响前i个数的最长不下降子序列长度

    于是我们考虑用动态规划

    设f[i]表示以a[i]结尾的最长不下降子序列长度,对于第i+1个数,要使以a[i+1]结尾的不下降子序列最长,应把a[i+1]接在前i个数中最长不下降子序列后面,同时要保证a[i+1]接上后不下降子序列的性质不被破坏,即a[i+1]所在序列中的倒数第二个数应不大于a[i+1],而倒数第二个数是什么取决于我们选择哪个子序列。于是我们枚举1→i的每个数,找到所有不大于a[i+1]的数并选择其中f值最大的作为转移到f[i+1]的状态。

    我们得到动态转移方程

    f[i]=max(f[j])+1(1≤j<i且a[i]≥a[j])

    具体代码如下

     1 #include <cstdio>
     2 int n,a[40005],f[40005],ans;
     3 int main()
     4 {
     5     int i,j;
     6     scanf("%d",&n);
     7     for (i=1;i<=n;i++) scanf("%d",&a[i]);
     8     f[1]=1;
     9     for (i=2;i<=n;i++)
    10     {
    11         for (j=1;j<i;j++)
    12           if (a[j]<=a[i] && f[j]>f[i])
    13             f[i]=f[j];
    14         f[i]++;
    15     }
    16     for (i=1;i<=n;i++)
    17       if (f[i]>ans)
    18         ans=f[i];
    19     printf("%d
    ",ans);
    20     return 0;
    21 }

    这样算法复杂度是O(n2),对于较大的数据可能就会TLE

    考虑优化

    显然一个数列中有多个长度不等的不下降子序列,而我们只想知道最长不下降子序列的长度,其它不下降子序列的长度可以忽略,于是我们可以用一点贪心的思想,只存储最长不下降子序列的长度

    我们用f[len]来存储最长不下降子序列,其中len表示最长不下降子序列的长度

    假设对于数组中前i个数,已找到长度为j的最长不下降子序列,对于第i+1个数a[i+1],

    若a[i+1]不小于当前最长不下降子序列的最后一个数,即a[i+1]≥f[j],显然把a[i+1]接在f[j]后面可以增大当前最长不下降子序列的长度;如果a[i+1]<f[j],我们就用a[i+1]更新已知的最长不下降子序列,我们希望不下降子序列尽可能长,所以我们需要不下降子序列前面部分的元素尽可能小,这样后面才能接上尽可能多的元素,所以每一次对已知最长不下降子序列的更新都要使序列中某个元素变小并且不破坏不下降的性质。考虑到已知最长不下降子序列是一个非递减序列,我们可以用二分找到序列中比a[i+1]大的第一个数的位置,并用a[i+1]把这个数换掉,这样既满足了把一个元素变小,又不会破坏不下降的性质,因为由二分的条件知这个位置前面的数一定比a[i+1]小且后面的数一定不比a[i+1]小。

    这样就把时间复杂度降到了nlogn

    简单介绍助我们实现代码的两个函数:lower_bound 与 upper_bound

    这两个函数用于二分查找关键字所在位置,lower_bound格式为lower_bound(起始地址,结束地址,要查找的数值),返回值为大于等于要查找的数值的元素的位置,可用于求最长不下降子序列,upper_bound格式相同,返回值为大于要查找的数值的元素的位置,可用于求最长上升子序列。由于两个函数都是二分查找,只能用于有序序列

    具体代码如下

     1 #include <algorithm>
     2 #include <cstdio>
     3 int n,a[40005],d[40005];
     4 int main()
     5 {
     6     int i,j,len=1;
     7     scanf("%d",&n);
     8     for (i=1;i<=n;i++) scanf("%d",&a[i]);
     9     d[1]=a[1];  //初始化
    10     for (i=2;i<=n;i++)
    11     {
    12         if (a[i]>=d[len]) d[++len]=a[i];  //如果可以接在len后面就接上,如果是最长上升子序列,这里变成>
    13         else  //否则就找一个最该替换的替换掉
    14         {
    15             j=std::upper_bound(d+1,d+len+1,a[i])-d;  //找到第一个大于它的d的下标,如果是最长上升子序列,这里变成lower_bound
    16             d[j]=a[i];
    17         }
    18     }
    19     printf("%d
    ",len);
    20     return 0;
    21 }

    第一次写算法写的不好多多指教orz

  • 相关阅读:
    Android获取手机内存和sd卡相关信息
    总结(创建快捷方式等)
    正则是个好东西
    Android自定义AlertDialog
    Eclipse生成author等注释
    day18 io多路复用
    json 模块
    re 模块
    random 模块
    hashlib 模块
  • 原文地址:https://www.cnblogs.com/rabbit1103/p/13904920.html
Copyright © 2011-2022 走看看