题目
给出一个字符串,长度小于一千。你可以执行三种操作:
- 删除一个字符
- 增加一个字符
- 修改一个字符
问,最少执行多少次操作可以使原字符串变成回文字符串。
解题思路
d p [ i ] [ j ] dp[i][j] dp[i][j]表示将区间 [ i , j ] [i,j] [i,j]修改成回文串的最小花费。
由于 d p dp dp的性质,当求 d p [ i ] [ j ] dp[i][j] dp[i][j]时, d p [ i + 1 ] [ j − 1 ] dp[i+1][j-1] dp[i+1][j−1]、 d p [ i + 1 ] [ j ] dp[i+1][j] dp[i+1][j]、 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j−1]都已经知道了。
如果
a
[
i
]
=
=
a
[
j
]
a[i]==a[j]
a[i]==a[j],那么
d
p
[
i
]
[
j
]
=
d
p
[
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],有 d p [ i ] [ j ] = d p [ i + 1 ] [ j ] + 1 dp[i][j]=dp[i+1][j]+1 dp[i][j]=dp[i+1][j]+1
- 删除 a [ j ] a[j] a[j],有 d p [ i ] [ j ] = d p [ i ] [ j − 1 ] + 1 dp[i][j]=dp[i][j-1]+1 dp[i][j]=dp[i][j−1]+1
- 增加一个和 a [ j ] a[j] a[j]相同的字符 a [ i ] a[i] a[i],有 d p [ i ] [ j ] = d p [ i + 1 ] [ j ] + 1 dp[i][j]=dp[i+1][j]+1 dp[i][j]=dp[i+1][j]+1
- 增加一个和 a [ i ] a[i] a[i]相同的字符 a [ j ] a[j] a[j],有 d p [ i ] [ j ] = d p [ i ] [ j − 1 ] + 1 dp[i][j]=dp[i][j-1]+1 dp[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],有 d p [ i ] [ j ] = d p [ i + 1 ] [ j − 1 ] + 1 dp[i][j]=dp[i+1][j-1]+1 dp[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;
}