网上看了很多解题报告也没看太懂,到底为什么这么做,除了为了节省内存之外用了滚动数组的方法,具体的思路还是要重点理解。
我们要求一个字符串,加入至少几个字符,使这个字符串变为回文串。那么怎么算呢,把这个串反过来,然后求这两个数列的最长公共子序列,那么这个公共子序列不就是原字符串变为回文串的基本结果么,之后我们要做的就是添加一些字符,融合原来字符串中的除了公共子序列的字符,变为最终的一个字符串,那么我们要加的字符个数就是原来字符串的长度减掉最长公共子序列的长度。
至于滚动数组,我觉得最好的理解方式就是求斐波那契数列的操作:
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; }