Time Limit: 3000MS | Memory Limit: 65536K | |
Total Submissions: 49842 | Accepted: 17150 |
Description
Input
Output
Sample Input
5 Ab3bd
题意:给定一个字符串,求对它最少进行多少次操作,可以变成一个回文串有这么个结论,需要加的字符的个数=原来字符串的长度-原来字符串和逆字符串的最长公共子序列的长度。然后用滚动数组即可。
#include<iostream> #include<cstring> #define N 5010 using namespace std; int dp[2][N]; char s1[N],s2[N]; int max(int a,int b) { return a>=b?a:b; } int main() { int n,i,j; while(cin>>n) { cin>>s1+1; for(i=1;i<=n;i++) s2[n-i+1]=s1[i]; memset(dp,0,sizeof(dp)); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { if(s1[i]==s2[j]) 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]); } } cout<<n-dp[n%2][n]<<endl; } return 0; }
另一种解法:
动态规划法。设字符串为S,长度为L,d[i][j]表示以第i个字符为首,第j个字符为尾的字符串构成回文最少需要添加的字符个数,i和j的初值分别为1、L。
如果S[i] == S[j],即字符串两端的字符相等,d[i][j] = d[i+1][j-1],
即d[i][j]等于去掉头尾后的字符串的d值。
如果S[i] != S[j],此时划分出两个子问题,求d[i][j-1]和d[i+1][j],它两中较小的值再加1即为d[i][j](加上的1个字符是用于和S[i]或者S[j]构成对称的)。
状态转移方程:
![]()
从上面的分析可以看出,这个问题的实质是求最长公共子序列,只是这两个序列分别是串S的前一部分和串S后一部分的逆序列。
#include <iostream> #include <cstdio> #include <cstring> using namespace std; #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAXSIZE 5005 //开始没有考虑内存问题,使用了int型,超内存限制,也可使用滚动数组解决 unsigned short d[MAXSIZE][MAXSIZE]; int ToPalindrome(char *s, int n) { int i, j, k; //只有一个字符时,不需要添加字符 for (i = 0; i < n; i++) { d[i][i] = 0; } //串长度为2时 for (i = 1; i < n; i++) { if (s[i-1] == s[i]) { d[i-1][i] = 0; } else { d[i-1][i] = 1; } } //串长度递增 for (k = 2; k < n; k++) { for (i = 0, j = k; j < n; i++, j++) { if (s[i] == s[j]) { d[i][j] = d[i+1][j-1]; } else { d[i][j] = MIN(d[i][j-1], d[i+1][j]) + 1; } } } return d[0][n-1]; } int main(void) { char str[MAXSIZE]; int n; while (scanf("%d", &n) != EOF) { getchar(); gets(str); printf("%d\n", ToPalindrome(str, n)); } return 0; }