大意:求一个字符串在通过在任意位置增加一个字符使得变为一个回文串的最小操作数及打印出该回文串。
思路:由于增加一个字符不太好操作,我们把问题转换一下。
这里需要用到一个结论,即增加字符变为回文串与减少字符变为回文串的最小操作数是相同的,这里可以用手模拟几种情况,就可以得出这个结论。
有了这个结论之后,我们就可以来写题了。
那么状态转移方程即为:
d[i][j] = d[i+1][j-1]; str[i] == str[j]
d[i][j] = min(d[i+1][j-1], d[i][j-1])+1; str[i] != str[j];
如何去打印路径呢?我们知道LCS中是通过记录当前操作,然后通过递归的方式来打印路径的,这里也类似。
由于回文串的特点是两边对称,所以,我们在把输入的字符串变为回文串的过程中可以通过左边的串来输出右边的串。
这样递归的特点即是: printf("%c", str[i]); print_ans(i+1,j -1); printf("%c", str[i]);
这样,即可保证字符串是左右对称的。
而且在进行动态规划时if(dp(i+1, j) < dp(i, j-1)输出的结果与if(dp(i+1, j) <= dp(i, j-1)的结果是不相同的,但同样都可以AC,具体的原因,自己思考一下吧。
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
using namespace std;
const int MAXN = 1010;
const int INF = 0x3f3f3f3f;
int d[MAXN][MAXN];
int path[MAXN][MAXN];
bool vis[MAXN][MAXN];
char str[MAXN];
void init()
{
memset(path, -1, sizeof(path));
memset(vis, 0, sizeof(vis));
}
int dp(int i, int j)
{
int &ans = d[i][j];
if(i >= j) return 0;
if(vis[i][j]) return ans;
vis[i][j] = 1;
if(str[i] == str[j]) { ans = dp(i+1, j-1); path[i][j] = 0;}
else if(dp(i+1, j) < dp(i, j-1)) {ans = dp(i+1, j)+1; path[i][j] = 1;} // <=与 <的结果不同
else { ans = dp(i,j-1)+1; path[i][j] = 2;}
return ans;
}
void print_ans(int i, int j)
{
if(i > j) return ;
if(i == j) printf("%c", str[i]);
if(path[i][j] == 0)
{
printf("%c", str[i]);
print_ans(i+1, j-1);
printf("%c", str[i]);
}
else if(path[i][j] == 1)
{
printf("%c", str[i]);
print_ans(i+1, j);
printf("%c", str[i]);
}
else if(path[i][j] == 2)
{
printf("%c", str[j]);
print_ans(i, j-1);
printf("%c", str[j]);
}
}
void solve()
{
init();
int n = strlen(str);
int ans = dp(0, n-1);
printf("%d ", ans);
print_ans(0, n-1);
printf("\n");
}
int main()
{
while(~scanf("%s", str))
{
solve();
}
return 0;
}