您的位置:首页 > 其它

单调递增最长子序列 (NYOJ 17) [动态规划]

2018-02-05 10:57 267 查看

单调递增最长子序列

时间限制:3000 ms  |  内存限制:65535 KB
难度:4

 
描述求一个字符串的最长递增子序列的长度
如:dabdbf最长递增子序列就是abdf,长度为4 
输入第一行一个整数0<n<20,表示有n个字符串要处理
随后的n行,每行有一个字符串,该字符串的长度不会超过10000输出输出字符串的最长递增子序列的长度样例输入
3
aaa
ababc
abklmncdefg

样例输出
    1
    3
    7

本题有多种解法:一:最长公共子序列法此方法用时较长,占用内存也较大,且仅适用于有规律且有限长度的子序列中,代码如下:
#include<stdio.h>
#include<string.h>
#define max(a,b) (a>b?a:b)
int dp[10010][30];
int main(void)
{
char a[10010];
char b[30]={'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
int n,len,i,j,k;
int sum,max;
scanf("%d",&n);
while(n--)
{
scanf("%s",a);
len=strlen(a);
for(i=1;i<=len;i++)
{
for(j=1;j<=26;j++)
{
if(a[i-1]==b[j-1])
{
dp[i][j]=dp[i-1][j-1]+1;
}
else
{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
/*      for(i=0;i<len;i++)
{
for(j=0;j<26;j++)
{
printf("%d ",dp[i][j]);
}
printf("\n");
}
*/
printf("%d\n",dp[len][26]);
}

return 0;
}


二:O(n^2) 最大子段和法

此方法采用了标记数组,记录每个当前状态下的最优解。

本题中的核心代码就是那两个for循环来更新m和dp数组中的值,

其中第一个循环中,每次都须将m置零 ,
其中第二个for循环的意思就是更新m的值,
使m的值为i位置字符之前的所有比i位置字符小的dp数组中所存值得最大值,直至第二个循环
结束,使i位置的dp数组为m的值加上1,即可。

 

代码如下:

#include<stdio.h>
#include<string.h>
char s[10010];
int dp[10010]={1};
int main(void)
{
int n,len;
int i,j,m;
scanf("%d",&n);
while(n--)
{
scanf("%s",s);
len=strlen(s);
for(i=1;i<len;i++)
{
m=0;
for(j=i-1;j>=0;j--)
{
if(s[i]>s[j]&&m<dp[j])
{
m=dp[j];
}
}
dp[i]=m+1;
}
m=dp[0];
for(i=0;i<len;i++)
{
if(m<dp[i])
{
m=dp[i];
}
}
printf("%d\n",m);
}
return 0;
}


三:O(n^2)  此方法是方法二的优化,方法二中的标记数组过长,此方法采用标记字符串,所以标记数组的长度缩减到26即可,本代码中用的长度为30

本题中使用了标记字符串,先将待求字符串中的第一个字符赋到标记字符串中,
并使用k对标记字符串进行计数,

然后依次读入待求字符串的字符,
并与标记字符串中的字符从后往前依次比较,

若标记字符串的字符比待求字符串的字符大,且此时处在标记字符串的起始位置
,则将此位置的待求字符赋到标记字符串中,

若标记字符串的字符比待求字符串的字符小,则将此处待求字符串的字符赋到标记字符串的下一个字符,如果此时已达到标记字符串的长度,则令k加1,并结束当前循环,重新开始比较,重新开始时,只有k增加了一,其余各数据均不变。

代码如下:

#include<stdio.h>
#include<string.h>
int main(void)
{
char s[10010];
char dp[35];
int n,len,i,j,k;
scanf("%d",&n);
while(n--)
{
scanf("%s",s);
len=strlen(s);
dp[0]=s[0];
k=1;
for(i=0;i<len;i++)
{
for(j=k-1;j>=0;j--)
{
if(dp[j]>s[i]&&j==0)
{
dp[0]=s[i];
}
if(dp[j]<s[i])
{
dp[j+1]=s[i];
if(j==k-1)
{
k++;
}
break;
}
}
}
dp[k]='\0';
printf("%d\n",k);
}

return 0;
}


四:O(n*lg n)

此方法是对方法三的优化,

最长递增子序列可以优化到 O(n*lg n)
通过优化方法二中的s[i]在dp[]什么位置,使用二分查找。
if(a[i]>dp[k]) {k=k+1;dp[k]=s[i];}
if(a[i]<dp[1]) dp[1]=s[i];
其他情况找到 dp[j]<s[i]<dp[j+1]  dp[j+1]=s[i];

注:虽然说此方法是最优的解法,但是在oj上面提交的时候,并没有方法三省时间,省内存。

#include<stdio.h>
#include<string.h>
#define N 10010
char s
,dp[30];
int work(int n)
{
int i,j,k,x,y,m;
dp[0]=s[0];
k=0;
for(i=1;i<n;i++)
{
if(dp[k]<s[i])
{
k++;
dp[k]=s[i];
continue;
}
x=0;
y=k;
while(x<=y)
{
if(s[i]<dp[x])
{
j=x;
break;
}
if(dp[y]<s[i])
{
j=y+1;
break;
}
m=(x+y)/2;
if(dp[m]<s[i])
{
x=m+1;
}
else if(s[i]<dp[m])
{
y=m-1;
}
else
{
j=m;
break;
}
}
dp[j]=s[i];
}
return k+1;
}
int main(void)
{
int n,i;
scanf("%d",&n);
while(n--)
{
scanf("%s",s);
printf("%d\n",work(strlen(s)));
}
return 0;
}


 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: