NYOJ 79 & 17 & 214 单调最长子序列问题(DP)

先解释下什么叫子序列。若a序列删去其中若干个元素后与b序列完全相同,则称b是a的子序列。

我们假定存在一个单调序列{An}(以递增序列为例),现在在其后面添加一个元素a(n+1),有两种情况:

1.a(n+1)>a(n)   。此时,a(n+1)可以添加到An序列的尾部,形成一个新的单调序列,并且此序列长度大于之前An的长度;

2.a(n+1)<=a(n)。此时,a(n+1)当然不可以添加到An序列的尾部。

经过分析,我们可以得出这样的结论:一个单调序列与其后面元素的关系仅与此序列的末尾元素有关

如此,便有了此题如下的dp解法:

建立一个一维数组dp[ ],用dp[i]保存长度为i的单调子序列的末尾元素的值,用top保存单调子序列的最大长度。

初始top=1,dp[1]=a1;

然后,我们从前向后遍历序列An(i : 2~n)。显然,我们会遇到两种情况:

1.a[i] > dp[top]。此时,我们把a[i]加入到长度为top的单调序列中,这时序列长度为top+1,top值也跟着更新,即dp[++top] = a[i];

2.a[i]<=dp[top]。此时,a[i]不能加入长度为top的单调序列,那它应该加入长度为多少的序列呢?

   做法是,从后向前遍历dp[ ] (j: top-1~1),直到满足条件 a[i] > dp[j],此时,类似于步骤1,我们可以把a[i]加入到长度为j的单调序列中,这时此序列长度为j+1,

   我们将dp[j+1]的值更新为a[i]。可是,为什么要更新它呢?

   因为a[i]一定小于dp[j+1]。为什么呢?如果a[i]不小于dp[j+1],我们找到的j就应该是j+1而不是j。那么,我们为什么要保留把dp[j+1]的最小值呢?

   因为对于相同长度的单调递增序列来说,末尾元素的值越小,其后元素加入此序列的可能性越大,也就是说,我们这样做,是为了防止丢失最优解。

   其中的奥妙,读者可以自己写一两组数据按照上面的步骤体验下,别出太弱的数据哦。O(∩_∩)O

   于是,我们很容易写出时间复杂度为O(n*n)的代码,如下:

[cpp]  view plain copy
  1. #include <stdio.h>  
  2. #include <string.h>  
  3. int main()  
  4. {  
  5.     int z,n,num[21],dp[21];  
  6.     scanf("%d",&z);  
  7.     while(z--)  
  8.     {  
  9.         memset(dp,0,sizeof(dp));  
  10.         scanf("%d",&n);  
  11.         for(int i=0;i<n;i++)  
  12.             scanf("%d",&num[i]);  
  13.         int top=1;  
  14.         dp[1]=num[0];  
  15.         for(int i=1;i<n;i++)  
  16.         {  
  17.             if(num[i] > dp[top])  
  18.             {  
  19.                 dp[++top] = num[i];  
  20.             }  
  21.             else  
  22.             {  
  23.                 int j;  
  24.                 for(j=top-1;j>=1;j--)  
  25.                     if(num[i] > dp[j])  
  26.                         break;  
  27.                 dp[j+1] = num[i];  
  28.             }  
  29.         }  
  30.         printf("%d\n",top);  
  31.     }  
  32.     return 0;  
  33. }  

   我们已经得出了答案,那么,我们能不能继续优化下时间复杂度呢?

   第一种情况显然不需要优化,那么,第二种情况可不可以呢?

   在第二种情况的时候,我们需要做的是:在单调序列dp[ ]中找出一个最大的j,使其满足a[i] > dp[j],看到“单调”你想到了什么?没错,是二分。

   如此,时间复杂度变为(N*logN);

[cpp]  view plain copy
  1.    
  2. #include <stdio.h>  
  3. #include <string.h>  
  4. int BinarySearch(int *a,int left,int right,int x)    //返回递增序列a[]中第一次a[i]>=x的位置  
  5. {  
  6.     if(a[left] >= x)  
  7.         return left;  
  8.     while(left <= right)  
  9.     {  
  10.         int mid = (left+right)/2;  
  11.         if(a[mid] >= x)  
  12.             right = mid-1;  
  13.         else  
  14.         {  
  15.             left = mid+1;  
  16.             if(a[left] >= x)  
  17.                 return left;  
  18.         }  
  19.     }  
  20. }  
  21. int main()  
  22. {  
  23.     int z,top,dp[10005];  
  24.     char num[10005];  
  25.     scanf("%d",&z);  
  26.     while(z--)  
  27.     {  
  28.         scanf("%s",num);  
  29.         dp[(top=1)]=num[0];  
  30.         int n=strlen(num);  
  31.         for(int i=1;i<n;i++)  
  32.         {  
  33.             if(num[i] > dp[top])  
  34.                 dp[++top]  = num[i];  
  35.             else  
  36.                 dp[BinarySearch(dp,1,top,num[i])] = num[i];  
  37.         }  
  38.         printf("%d\n",top);  
  39.     }  
  40.     return 0;  
  41. }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值