P1435 回文字串
题目背景
IOI2000第一题
题目描述
回文词是一种对称的字符串。任意给定一个字符串,通过插入若干字符,都可以变成回文词。此题的任务是,求出将给定字符串变成回文词所需要插入的最少字符数。
比如 “Ab3bd”插入2个字符后可以变成回文词“dAb3bAd”或“Adb3bdA”,但是插入少于2个的字符无法变成回文词。
注:此问题区分大小写
输入输出格式
输入格式:
一个字符串(0<strlen<=1000)
输出格式:
有且只有一个整数,即最少插入字符数
输入输出样例
Ab3bd
输出样例#1:
2
分析:
解题思路:该题说是考察如何将一个字符串添加成一个回文串的,不如说是一道求最长公共自序列的变式题,为啥这么说呢?肯定是有原因在里面的
-
首先,我们要摸清回文串的特性,回文就是正着读反着读一样,一种非常对称不会逼死强迫症的字符串;这就是我们的突破口。。。你难道以为是逼死强迫症么?哈哈,太天真了,突破口其实是因为回文正着读反着读都相同的特性。。。这样我们就可以再建一个字符数组存储倒序的字符串
- 我们先分析下样例:ab3bd,
它的倒序是:db3ba
这样我们就可以把问题转化成了求最长公共自序列的问题,为啥可以这么转化呢?
它可以这么理解,正序与倒序“公共”的部分就是我们回文的部分,如果把正序与倒序公共的部分减去你就会惊奇的发现剩余的字符就是你所要添加的字符,也就是所求的正解
ad da把不回文的加起来就是我们梦寐以求的东西:回文串(卧槽?太没追求了)
把ad,da加起来成回文串就是adb3bda,所以这个问题就可以转化成了求最长公共自序列的问题,将字符串的长度减去它本身的“回文的”(最长公共自序列)字符便是正解
- 找到解题思路后我们就可以开始写了,最长公共自序列问题是个经典的dp问题,
最容易想到的方法就是开个二维数组dp【i】【j】,i,j分别代表两种状态;
那么我们的动态转移方程应该就是if(str1[i] == str2[j]) dp[i][j]=dp[i-1][j-1]+1;
Else dp[i][j] = max(dp[i-1][j], dp[i][j-1];
依此即可解出最长公共自序列,用字符串长度减去即是正解
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<cstdlib> 5 using namespace std; 6 int n; 7 int dp[5001][5001]; 8 char str1[5001],str2[5001]; 9 int main() 10 { 11 //freopen("palindrome.in", "r", stdin); 12 //freopen("palindrome.out", "w", stdout); 13 scanf("%s", str1+1); 14 n = strlen(str1+1); 15 for(int i = 1; i <= n; i++) 16 str2[i] = str1[n-i+1]; //做一个逆序的字符串数组 17 for(int i = 1; i<=n; i++) 18 for(int j = 1; j <= n; j++) 19 if(str1[i] == str2[j]) 20 dp[i][j] = dp[i-1][j-1] + 1; //最长公共自序列匹配 21 else 22 dp[i][j] = max(dp[i-1][j], dp[i][j-1]); //不匹配的往下匹配状态 23 printf("%d ", n-dp[n][n]); //字符串长度减去匹配出的最长公共自序列的值 24 return 0; //即需要添加的字符数 25 }
可这并不是最优解法,只是能ac这道题而已 若是将内存限制改为2MB呢?
不过,没关系,正常开一个5001*5001的数组一定会爆掉的,此时你是不是在思考另一种解决方案来优化一下空间复杂度,如果我可以把它用一维数组代替二维数组中的状态量是不是也可以求出正解
没错。它真的能
求出正解;
如果你仔细研究一下就会发现每次搜索到str1的第i个元素的时候,数组dp【】【】真正使用到的元素仅仅是dp【i】【j】和dp【i-1】【k】(0 <= k <= n(n = strlen(str1))
即dp【】【】的第一下标从0-->i-2就没有用处了,因此我们可以开辟两个滚动数组来降低空间复杂度
Dp1【】用来记录当前状态,dp2【】用来记录之前的状态也就相当于刚才的dp【i-1】【j】
动态转移方程应该这么表达if(str1[i] == str2[i]) dp1[j] = dp2[j-1] +1;
Else dp1[j] = max(dp1[j-1], dp2[j]);
源代码在此,天下我有:
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<cstdlib> 5 using namespace std; 6 int n; 7 int dp1[5001],dp2[5001]; //此处用两个滚动数组记录,一个记录之前的状态,一个记录此时的状态 8 char str1[5001],str2[5001]; 9 int main() 10 { 11 //freopen("palindrome.in", "r", stdin); 12 //freopen("palindrome.out", "w", stdout); 13 scanf("%s", str1+1); 14 n = strlen(str1+1); 15 for(int i = 1; i <= n; i++) 16 str2[i] = str1[n-i+1]; //做一个逆序的字符串数组 17 for(int i = 1; i<=n; i++) 18 { 19 for(int j = 1; j <= n; j++) 20 if(str1[i] == str2[j]) 21 dp1[j] = dp2[j-1]+1; //“发现”匹配就记录 22 else 23 dp1[j] = max(dp1[j-1],dp2[j]); //不匹配就匹配后面的状态 24 memcpy(dp2, dp1, sizeof(dp1)); //记录之前的状态“滚动”匹配 25 } 26 printf("%d ", n-dp1[n]); //字符串长度减去匹配出的最长公共自序列的值 27 return 0; //即需要添加的字符数 28 }
ps:
滚动数组交换的时候,感觉直接交换索引比直接交换dp1和dp2整个数组要优。