Time Limit: 3000MS | Memory Limit: 65536K | |
Total Submissions: 55018 | Accepted: 19024 |
Description
Input
Output
Sample Input
5 Ab3bd
Sample Output
2
【题意】 给你一个长度为n的字符串,问最少再添多少字符能组成一个回文串;
【分析】
原字符串:Ab3bd
翻转后串:db3ba
二者有重复子串b3b,若想构成回文串,必须要再添加除重复子串外的其他字符。如:Adb3bdA
下面的问题就是求原字符串与翻转后串的最长公共子串,即LCS问题;
【LCS问题】
标记s1,s2字符位置变量i,j,令dp[i][j]为字符串s1[1~i],s2[1~j]的最长公共子串的长度;可知状态转移方程如下:
dp[i][j] = s1[i] == s2[j] ? dp[i-1][j-1] : max(dp[i-1][j], dp[i][j-1]);
【注意】
对于本题,n的范围是[3,5000],若直接开5000*5000的二维数组会内存超限(当然听说用short int会AC飘过);
【滚动数组】
滚动数组的作用在于优化空间。主要应用在递推或动态规划中(如01背包问题)。因为DP题目是一个自底向上的扩展过程,我们常常需要用到的是连续的解,前面的解往往可以舍去。所以用滚动数组优化是很有效的。利用滚动数组的话在n很大的情况下可以达到压缩存储的作用。
例如本题,dp[i][j]的值仅仅取决于dp[i-1][j-1], dp[i][j-1], dp[i-1][j];再直白地说,只需要保留下i-1时的状态,就可以求出i时的状态;所以dp完全可以只开一个2*5000的数组求解;
或许有人问j为什么不能也开成2? 这很好说明,因为j是随i不断循环的,i增加一个j全部循环一次,所以i在不断变化时需要不断j全部的信息,我们完全也可以令i随j不断变化,这样仅仅改变成5000*2,其他完全一样;
【代码】
1 /*LCS*/ 2 3 #include<iostream> 4 #include<cstdio> 5 #include<cstdlib> 6 #include<cstring> 7 using namespace std; 8 const int maxn = 5010; 9 char s1[maxn], s2[maxn]; 10 int n; 11 int dp[3][maxn]; 12 13 void LCS() 14 { 15 memset(dp, 0, sizeof(dp)); 16 17 for(int i = 1; i <= n; i++) 18 { 19 for(int j = 1; j <= n; j++) 20 { 21 //cout << s1[i] << " " << s2[j] << endl; 22 if(s1[i] == s2[j]) 23 dp[i%2][j] = dp[(i-1)%2][j-1]+1; 24 else 25 dp[i%2][j] = max(dp[(i-1)%2][j], dp[i%2][j-1]); 26 } 27 } 28 //cout << dp[n%2][n] << endl; 29 printf("%d\n", n-dp[n%2][n]); 30 31 32 } 33 34 int main() 35 { 36 while(~scanf("%d", &n)) 37 { 38 scanf("%s", s1+1); 39 40 for(int i = 0; i < n; i++) 41 s2[i+1] = s1[n-i]; 42 43 LCS(); 44 45 } 46 return 0; 47 }