题目大意:
给定一个长M,由N个小写字母构成的字符串,每插入或者删除一个字符都需要一定的花费,增删字符的花费不同,问怎样可以使这个字符串变成一个回文串,且花费最小。
思路:
由于在一头加上一个字母和在另一头减去一个字母对于得到一个回文串来讲效果是等价的,但是这两种操作的花费可能不同,因此,需要添加或者删除字母的时候只需要选择这两种里面花费较小的那一种。假设已经将区间[i,j]整理成为一个回文串,当DP到区间[i,j+1]时,我们可以在i-1的位置添加一个s[j+1]字符,或者将在j+1处的字符删除,得到一个新的回文串。因此从[i,j]到[i,j+1]的状态转移只相差一个增删s[j+1]字符的最小花费。
接下来核心代码和求最长回文子序列的模板代码就比较类似了。
附最长回文子序列基础教程:
http://www.cnblogs.com/AndyJee/p/4465696.html
代码:
#include<stdio.h>
#include<iostream>
#include<math.h>
#include<algorithm>
#include<limits.h>
#include<string.h>
using namespace std;
string s;
int n,m;
int cost[30];
int dp[2006][2006];//dp[i][j] 存储从 i 到 j 这一段转换成回文子序列需要的最小花费
int main(){//14584k,32ms
ios::sync_with_stdio(false);//将stdio解除绑定,使得cin与scanf效率相差无几
int a,b;
char t;
while(cin>>n>>m){
cin>>s;
while(n--){
cin>>t>>a>>b;
cost[t-'a']=min(a,b);
}
for(int i=m-1;i>=0;--i){
dp[i][i]=0;
for(int j=i+1;j<m;++j){
if(s[i]==s[j]) dp[i][j]=dp[i+1][j-1];
else dp[i][j]=min((dp[i+1][j]+cost[s[i]-'a']),(dp[i][j-1]+cost[s[j]-'a']));
}
}
//i和j先循环哪个都行,只要保证从下向上(i递减) && 从左向右(j递增) && i<=j
// for(int j=0;j<m;++j){
// dp[j][j]=0;
// for(int i=j-1;i>=0;--i){
// if(s[i]==s[j]) dp[i][j]=dp[i+1][j-1];
// else dp[i][j]=min((dp[i+1][j]+cost[s[i]-'a']),(dp[i][j-1]+cost[s[j]-'a']));
// }
// }
printf("%d\n",dp[0][m-1]);
}
return 0;
}
为了节省空间,可以只开两行的数组,因为每次更新只需要相邻行的数据。
参考链接:
http://www.2cto.com/kf/201311/258795.html
代码:
#include<stdio.h>
#include<iostream>
#include<math.h>
#include<algorithm>
#include<limits.h>
#include<string.h>
using namespace std;
string s;
int n,m,ans;
int cost[30];
int dp[2][2006];
int main(){//732k,16ms
ios::sync_with_stdio(false);
int a,b;
char t;
while(cin>>n>>m){
cin>>s;
while(n--){
cin>>t>>a>>b;
cost[t-'a']=min(a,b);
}
int now=0;
for(int i=m-1;i>=0;--i,now=!now){
dp[now][i]=0;
for(int j=i+1;j<m;++j){//每次更新这一行剩下的
if(s[i]==s[j]) dp[now][j]=dp[!now][j-1];
else dp[now][j]=min((dp[!now][j]+cost[s[i]-'a']),(dp[now][j-1]+cost[s[j]-'a']));
}
}
if(m%2) ans=dp[0][m-1];//字符串长度为奇数,则now进行偶数次反转,结果在第0行
else ans=dp[1][m-1];//字符串长度为偶数,结果在第1行
printf("%d\n",ans);
}
return 0;
}