POJ 1159 Palindrome

网上看了很多解题报告也没看太懂,到底为什么这么做,除了为了节省内存之外用了滚动数组的方法,具体的思路还是要重点理解。

我们要求一个字符串,加入至少几个字符,使这个字符串变为回文串。那么怎么算呢,把这个串反过来,然后求这两个数列的最长公共子序列,那么这个公共子序列不就是原字符串变为回文串的基本结果么,之后我们要做的就是添加一些字符,融合原来字符串中的除了公共子序列的字符,变为最终的一个字符串,那么我们要加的字符个数就是原来字符串的长度减掉最长公共子序列的长度。

至于滚动数组,我觉得最好的理解方式就是求斐波那契数列的操作:

for(i=2;i,=n;i++)
{
f[0]=f[1];
f[1]=f[2];
f[2]=f[0]+f[1];
}

我觉得这是最好的理解方式,因为我们所求的结果是一次递增的,每次都只用到前面一个或者两个的结果,至于更前面的我们用不到的,所以我们的数据只需要保存三个就够了,因为每次我们最多用掉三个数据,而每次用的三个也是前面的数据用相同的方法求出来了。


在求两个序列的最长公共子序列的时候,我们采用二元组描述问题状态。dp[i,j],两个序列第i,第j个字符之前的最长公共子序列的长度是多少。而你手推一下就知道了,每次求一个位置的时候,它只会用到之前的dp[i-1,j] dp[i,j-1]这两个位置的解。因为从底层递归上来,每一个元素都是当前位置之前的最大公共子序列的最大长度,所以并不需要之前的数据,只需要连个临近的数据即可。


但是因为题目中的数据范围过大,直接做的话肯定会超时,所以我们便找到一个类似斐波那契数列的循环节点,利用滚动数据的方法,节省空间,之后发现,我们只要用dp[2][5000],就可以达到dp[5000][5000]的效果,时间上是没什么优化,但是 内存上绝对大大的优化了。



PS:代码开头的注释部分是让我理解这个题目的精髓


//Palindrome//
//题目链接:http://poj.org/problem?id=1159

设a[i]是这个字符串,b[i]是这个字符串的逆序串。
那么a[i],b[i]的最长公共子序列就是所求的字符串里拥有的最大的回文串。
然后用总串长减去最大的回文串长度即为所求。



#include<stdio.h>
#include<string.h>
#define max(x,y) (x)>(y)?(x):(y)
int dp[2][5555];
char zheng[5555];
char fan[5555];
int n;
void reserve()
{
	int t=n;
	int i,j;
	for(i=0;i<n;i++)
		fan[i]=zheng[--t];
}
int main()
{
	int i,j,k;
	int temp;
	while(scanf("%d",&n)!=EOF)
	{
		scanf("%s",zheng);
		memset(dp,0,sizeof(dp));
		reserve();
		for(i=1;i<=n;i++)
		{
			for(j=1;j<=n;j++)
			{
				if(zheng[i-1]==fan[j-1])
					dp[i%2][j]=dp[(i-1)%2][j-1]+1;
				else
				dp[i%2][j]=max(dp[(i-1)%2][j],dp[i%2][j-1]);
			}
		}
			printf("%d\n",n-dp[n%2][n]);
	}
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值