题目
给出一个字符串,长度小于一千。你可以执行三种操作:
- 删除一个字符
- 增加一个字符
- 修改一个字符
问,最少执行多少次操作可以使原字符串变成回文字符串。
解题思路
dp[i][j]dp[i][j]dp[i][j]表示将区间[i,j][i,j][i,j]修改成回文串的最小花费。
由于dpdpdp的性质,当求dp[i][j]dp[i][j]dp[i][j]时,dp[i+1][j−1]dp[i+1][j-1]dp[i+1][j−1]、dp[i+1][j]dp[i+1][j]dp[i+1][j]、dp[i][j−1]dp[i][j-1]dp[i][j−1]都已经知道了。
如果a[i]==a[j]a[i]==a[j]a[i]==a[j],那么dp[i][j]=dp[i+1][j−1]dp[i][j]=dp[i+1][j-1]dp[i][j]=dp[i+1][j−1]
否则,也就是a[i]!=a[j]a[i]!=a[j]a[i]!=a[j],有五种方案:
- 删除a[i]a[i]a[i],有dp[i][j]=dp[i+1][j]+1dp[i][j]=dp[i+1][j]+1dp[i][j]=dp[i+1][j]+1
- 删除a[j]a[j]a[j],有dp[i][j]=dp[i][j−1]+1dp[i][j]=dp[i][j-1]+1dp[i][j]=dp[i][j−1]+1
- 增加一个和a[j]a[j]a[j]相同的字符a[i]a[i]a[i],有dp[i][j]=dp[i+1][j]+1dp[i][j]=dp[i+1][j]+1dp[i][j]=dp[i+1][j]+1
- 增加一个和a[i]a[i]a[i]相同的字符a[j]a[j]a[j],有dp[i][j]=dp[i][j−1]+1dp[i][j]=dp[i][j-1]+1dp[i][j]=dp[i][j−1]+1
- 将a[i]a[i]a[i]修改成a[j]a[j]a[j] 或 将a[j]a[j]a[j]修改成a[i]a[i]a[i],有dp[i][j]=dp[i+1][j−1]+1dp[i][j]=dp[i+1][j-1]+1dp[i][j]=dp[i+1][j−1]+1
可以发现删除和增加操作转移方程一样,因此总共三种转移方式。
代码
#include <bits/stdc++.h>
using namespace std;
int dp[1010][1010]; //dp[i][j]表示将区间[i,j]修改成回文串的最小花费
char s[1010];
int CA = 0;
void solve()
{
scanf("%s", s + 1);
int n = strlen(s + 1);
memset(dp, 0x3f3f3f3f, sizeof dp);
for (int i = 1; i <= n; i++)
dp[i][i] = 0;
for (int len = 2; len <= n; len++)
{
for (int l = 1; l + len - 1 <= n; l++)
{
int r = l + len - 1;
if (s[l] == s[r])
{
if (len == 2)
dp[l][r] = 0;
else
dp[l][r] = dp[l + 1][r - 1];
}
else
{
if (len == 2)
dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]) + 1;
else
dp[l][r] = min(dp[l + 1][r], min(dp[l][r - 1], dp[l + 1][r - 1])) + 1;
}
}
}
printf("Case %d: %d\n", ++CA, dp[1][n]);
}
int main()
{
int t;
cin >> t;
while (t--)
solve();
return 0;
}