题目背景
IOI2000第一题
题目描述
回文词是一种对称的字符串。任意给定一个字符串,通过插入若干字符,都可以变成回文词。此题的任务是,求出将给定字符串变成回文词所需要插入的最少字符数。
比如 “Ab3bd”插入2个字符后可以变成回文词“dAb3bAd”或“Adb3bdA”,但是插入少于2个的字符无法变成回文词。
注:此问题区分大小写
输入格式
一个字符串(0<strlen<=1000)
输出格式
有且只有一个整数,即最少插入字符数
输入输出样例
输入: 输出: Ab3bd 2
下面是这道题的题解:
这道题我一共有两种解法,下面我会把这两种解法都分享给大家:
第一种解法
第一种解法是用dp来解:
解题思路:这道题可以看做是一道求最长公共子序列的一道题(经典dp问题)!
为什么这么说呢,首先,回文串的特性就是正着读反着读都一样,一组对称的字符串。所以我们把这个字符串倒序放置也是和原来一样的。
这仿佛就找到了一个突破口。
正序与倒序“公共”的部分就是我们回文的部分,如果把正序与倒序公共的部分减去你就会惊奇的发现剩余的字符就是你所要添加的字符,也就是所求的正解!
找到解题思路后我们就可以开始写了,最长公共自序列问题是个经典的dp问题,
最容易想到的方法就是开个二维数组dp[i][j],i,j分别代表两种状态;
那么我们的动态转移方程应该就是if(a[i]==b[j]) dp[i][j]=dp[i-1][j-1]+1;
依此即可解出最长公共自序列,用字符串长度减去即是正解
由于我比较懒,下面有不直接粘贴代码了。
如果你想要更优的解法,就可以把二维数组变一维,不过这里就不说了(a了就行了,哪里呢么多事)
第二种解法
一样是dp。。。。。。
不过这次利用的是区间dp。
区间dp就很好理解了。
不解释原因了,直接上思路:
状态:dp[i][j]从i到j区间内最长回文子序列长度。
转移方程:dp[i][j]=dp[i+1][j-1]+2 i和k能配对
max(dp[i+1][j],dp[i][j-1]) i和k不能配对
状态:dp[i][i]=1
答案:dp[1][n]
复杂度:O(n^2)
下面是是dp部分代码:
for(int len=2;len<=n;len++) { for(int i=1;i<=n-len+1;i++) { int j=i+len-1; if(a[i]==a[j]) dp[i][j]=dp[i+1][j-1]+2; else dp[i][j]=max(dp[i+1][j],dp[i][j-1]); } }
输入问题注意从a[1]开始输入
输出要用总长度-回文长度
下面是全部代码(区间dp)
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<algorithm> #define inf 100000000 //状态:dp[i][j]从i到j区间内最长回文子序列长度。 //转移方程:dp[i][j]=dp[i+1][j-1]+2 i和k能配对 // max(dp[i+1][j],dp[i][j-1]) i和k不能配对 //状态:dp[i][i]=1 //答案:dp[1][n] //复杂度:O(n^2) char a[1001]; int dp[1001][1001]; using namespace std; int main() { scanf("%s",a+1); int n=strlen(a+1); for(int i=1;i<=n;i++) { dp[i][i]=1; } for(int len=2;len<=n;len++) { for(int i=1;i<=n-len+1;i++) { int j=i+len-1; if(a[i]==a[j]) dp[i][j]=dp[i+1][j-1]+2; else dp[i][j]=max(dp[i+1][j],dp[i][j-1]); } } cout<<n-dp[1][n]; return 0;//不写return 0,考试就爆零 }
最后祝大家AC所有题!
给个赞再走呗?