题意:有一个由n个小写字母组成的,长度为m的字符串,可以对其通过增加字符或者删除字符来使其变成回文。而增加或者删除字符都有一个花费,求解使该字符串变成回文的最小花费。
思路:DP。dp[i][j]表示串str[i~j]变成回文的最小代价,故状态转移方程为:
当str[i] == str[j]时 dp[i][j] = dp[i+1][j-1];
当str[i] != str[j]时 dp[i][j] = min( dp[i+1][j]+add[str[i]-'a'], dp[i][j-1]+add[str[j]-'a'],
其实dp很难逃出3种思路:
1、一维线性dp:每次考虑i时,选择最优子问题要么在i-1,要么在1...i-1里;
2、二维线性dp:考虑(i,j)子问题时,选择最优子问题要么在(i+1,j)、(i,j-1),要么在i<= k <=j,在k里;
3、树形dp:考虑i节点最优时,选择子节点最优,一般融合了01背包dp的双重dp。
上面3中模式也是我在做题后才发现的。
这个dp题其实就可以仿照第2中思路。
假设一个字符串Xx....yY;对于求这个字符串怎么求呢?
分4中情况讨论:
1、去掉X,取x....yY回文;
2、去掉Y,取Xx....y回文;
3、在左边加上X,取Xx....yYX回文;
4、在右边加上Y,取YXx....y回文。
至于去掉X、Y肯定没有第1、2中情况合算;加上X、Y肯定没有第3、4中情况合算。
因此令dp[i][j]为i...j要变成回文字符串的最小代价。
方程:
dp[i][j] = min{
其实分析发现,对于X而言,只要去 去掉 和加上 X 最小代价就行(因为前面dp串一样),Y同理。
因此最后得出:
dp[i][j] = min{
dp时候还有些注意事项:
比如当X和Y字符一样时,则在dp时必须先为x...y的最小代价。
源代码:(14056K, 79MS)
#include<iostream>
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
const int MAX = 2005;
int dp[MAX][MAX];
int add[30], del[30];
int main(){
int n, m, i, j, len;
char c, str[MAX];
cin >> n >> m >> str;
for(i = 0; i < n; i ++){
cin >> c;
cin >> add[c-'a'] >> del[c-'a'];
}
for(len = 1; len < m; len ++)
for(i = 0; i + len < m; i ++){
j = i + len;
if(str[i] == str[j]){
dp[i][j] = dp[i+1][j-1];
}else{
dp[i][j] = min(
min(dp[i+1][j]+add[str[i]-'a'], dp[i][j-1]+add[str[j]-'a']),
min(dp[i+1][j]+del[str[i]-'a'], dp[i][j-1]+del[str[j]-'a']));
}
}
cout << dp[0][m-1] << endl;
return 0;
}