单调递增最长子序列

来源:互联网 发布:手机唱歌录音软件 编辑:程序博客网 时间:2024/06/05 23:46

一.概述

先解释下什么叫子序列。若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]的最小值呢?

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

 

二.解法

思路一:

用F[i]代表若递增子序列以ai结束时的它的最长长度。当F[1]到F[X-1]的值都已经确定,我们要求F[X],只需要将a[X]与a[1]..a[j]..a[x-1]依次比较,若a[x] > a[j],我们就可以把a[x]接在以a[j]结尾的最长子序列后,形成一个新的递增子序列;当我们求得所有的以a[j]结尾的子序列接上a[x]后的长度的最大值,就是F[x].

递推关系:

F[1] = 1;

F[i] = max{1,F[j] + 1}(aj < ai && j < i)

代码:

/**********************************   日期:2013-3-25*   作者:SJF0115*   题号: 题目17: 单调递增最长子序列*   来源:http://acm.nyist.net/JudgeOnline/problem.php?pid=17*   结果:AC*   来源:南阳理工OJ*   总结:**********************************/#include<stdio.h>#include<string.h>char array[10001];int MaxLen[10001];//最长递增子序列void LIS(){memset(MaxLen,0,sizeof(MaxLen));int len = strlen(array);for(int i = 0;i < len;i++){MaxLen[i] = 1;for(int j = 0;j < i;j++){if(array[i] > array[j]){if(MaxLen[i] < 1 + MaxLen[j]){MaxLen[i] = 1 + MaxLen[j];}}}}}int main(){int N,i,len,Max;//freopen("C:\\Users\\SJF\\Desktop\\acm.txt","r",stdin);scanf("%d",&N);//N组测试数据while(N--){Max = 0;scanf("%s",array);LIS();len = strlen(array);//输出最大长度for(i = 0;i < len;i++){if(Max < MaxLen[i]){Max = MaxLen[i];}}printf("%d\n",Max);}return 0;}


 

思路二:O(n ^2)

使用一个数组  ans[] 来存储当前状态下最长递增子序列,用 count 来记录当前状态下的最大值,s[0...strlen(s)-1] 为输入数组;

当遍历到 s[i] 时,在 ans[0...count-1] 数组中,找到第一个位置使 ans[j]<s[i] ; ans[j+1] = s[i]; 当然如果 j==count-1时  count++;

这样做的最终效果是,在ans[0...count-1]数组中总是放的是最小符合要求的值。如:1342 ;ans中方的是 124 虽然不是真正的答案,但长度一样。

注意边界条件就 ans[0]  的处理:如案例 412 ;45123 ;

代码:

#include<iostream>#include<cstring>#define N 10010using namespace std;char s[N];char ans[N];int count;int main(){    int test,len,i,j;    cin>>test;    while(test--)    {        cin>>s;        len=strlen(s);        count=1;        ans[0]=s[0];        for(i=0;i<len;i++)        {            for(j=count-1;j>=0;j--)            {                if(j==0&&ans[0]>s[i]) ans[0]=s[i];                if(ans[j]<s[i])                {                    ans[j+1]=s[i];                    if(j==count-1)  count++;                    break;                }            }        }        ans[count]='\0';//        cout<<ans<<endl;        cout<<count<<endl;    }    return 0;}


思路二:O(nlogn)

通过优化方法二中的a[i]在ans[]什么位置,使用二分查找。

代码:

#include<stdio.h>int ss[104000];int dp[104000];int Search(int x,int len){    int mid,left,right;    left=1;    right=len;    mid=(right+left)/2;    while(left<=right)    {        if(x>dp[mid])        {            left=mid+1;        }        else            if(x<dp[mid])            {                right=mid-1;            }            else            {                return mid;            }            mid=(right+left)/2;    }    return left;}int main(){    int N,i,j,count;    while(~scanf("%d",&N))    {        for(i=0;i<N;i++)        scanf("%d",&ss[i]);        count=1;        dp[0]=-999999;        dp[1]=ss[0];        for(i=1;i<N;i++)        {            j=Search(ss[i],count);           // printf("%d ",j);  //查找更新每次都选最小的            dp[j]=ss[i];            if(j>count) count=j;        }        printf("%d\n",count);    }    return 0;}


 

0 0