动态规划poj1631--最长上升子序列

本小妞迷上赌 提交于 2020-01-25 05:25:23

题目大意:

一个数的序列bi,当b1 < b2 < ... < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, ..., aN),我们可以得到一些上升的子序列(ai1, ai2, ..., aiK),这里1 <= i1 < i2 < ... < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).

你的任务,就是对于给定的序列,求出最长上升子序列的长度。

法一:O(n^2)

思路:转化成子问题:求以ak(k=1,2,3。。。N)为终点(即上升子序列中最右边的数)的最长上升子序列的长度。该问题只和数字的位置有关,因而序列中数的位置k就是“状态”,而状态“k“对应的值就是以ak作为终点的最长上升子序列的长度。假定maxlen(k)表示以ak作为终点的最长上升子序列的长度,则:

maxlen(1)=1;

maxlen(k)=max{maxlen(i):1<i<k且ai<ak且k!=1}+1

这个状态转移方程的思想是:maxlen(k)的值就是在ak左边,终点小于ak,且长度最大的那个上升子序列的长度再加1.因为ak左边任何终点小于ak的子序列加上ak后就能形成一个更长的上升子序列。

以下是代码:

#include <stdio.h> 
#define  MAX 1000  //千万别加‘;’
int seq[MAX+10]; 
int seqlen[MAX+10]; 
int main() 

    int i,j,k,N,max,maxlen=1; 
    scanf("%d",&N); 
         seqlen[1]=1;               //seqlen数组存以第i个数为终点的最长上升序列  
       for(i=1;i<=N;i++)  
     scanf("%d",&seq[i]);       //seq数组保存序列数组 
    for (i=2;i<=N;i++) 
    { 
        max=0;                                 //max是用于计算从seq[1]到seq[i-1]的最长上升子序列的值
        for (j=1;j<=i-1;j++) 
        { 
            if(seq[j]<seq[i]&&seqlen[j]>max)  //在前i-1个序列中,寻找以终点小于seq[i]的最长的子序列,即最优子状态 
                max=seqlen[j]; 
        } 
        seqlen[i]=max+1; 
        if(seqlen[i]>maxlen)           //seqlen中保存的是第i个数为终点的最长上升序列,找出这个数组中最大的值即为最优序列长度 
            maxlen=seqlen[i]; 
    } 
    printf("%d\n",maxlen); 
    return 0; 
}

法二:复杂度O(nlogn)

但法一有个弱点:若MAX的值太大就会超时,因而需要优化。

思路:考虑两个数a[x]和a[y],x<y且a[x]<a[y],且dp[x]=dp[y],当a[t]要选择时,到底取哪一个构成最优的呢?显然选取a[x]更有潜力,因为可能存在a[x]<a[z]<a[y],这样a[t]可以获得更优的值。在这里给我们一个启示,当dp[t]一样时,尽量选择更小的a[x].

    按dp[t]=k来分类,只需保留dp[t]=k的所有a[t]中的最小值,设d[k]记录这个值,d[k]=min{a[t],dp[t]=k}。

    这时注意到d的两个特点(重要):

1. d[k]在计算过程中单调不升;           

2. d数组是有序的,d[1]<d[2]<..d[n]。

    利用这两个性质,可以很方便的求解:

1. 设当前已求出的最长上升子序列的长度为len(初始时为1),每次读入一个新元素x:

2. 若x>d[len],则直接加入到d的末尾,且len++;(利用性质2)

   否则,在d中二分查找,找到第一个比x小的数d[k],并d[k+1]=x,在这里x<=d[k+1]一定成立(性质1,2)。

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
#define M 41000
int a[M];
int d[M];
int binsearch(int key,int *d,int low,int high)
{
 
 while(low<=high)
 {int mid=(low+high)/2;     //mid=(low+high)>>1
 if(d[mid]<key&&d[mid+1]>=key)    //此处的二分法是为了找出d【】数组中从头开始第一个比key小的值,然后插入key
 return mid;
 else
  if(d[mid]>key)
  high=mid-1;
  else low=mid+1;
 }
}
int LIS(int *a,int n,int *d)
{
 int i,j,len=1;
 d[1]=a[1];
 for(i=2;i<=n;i++)
 {
  if(d[len]<a[i])
  j=++len;
  else
  j=binsearch(a[i],d,1,len)+1;  //此处的加1不能忘了。
  d[j]=a[i];
 }
 return len;
}
int main()
{
 int m,k,t;
 scanf("%d",&t);
 while(t--)
    {
      scanf("%d",&m);
  for(k=1;k<=m;k++)
  scanf("%d",&a[k]);
  printf("%d\n",LIS(a,m,d));
 }
 return 0;
}

 

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!