题目链接:3280 -- Cheapest Palindrome
题意: 给你一个字符串,然后给你每个字母添加或者删除所需要的代价,问你把原序列变成回文串的最小代价是多少?
输入:第一行给出序列中含有的不同字母n和序列长度l
接下来一行给出序列
接下来n行分别给出每个字母添加或者删除所需要的最小代价
分析:这是一道用区间DP来解决的题目,设f[i][j]表示将原序列中第i个字符到第j个字符变为回文序列的最小代价,那显然有f[i][i]=0,因为单个字符也算是回文串,关键是来看一下怎么进行动态转移
分两种情况进行讨论:
(1)s[i]==s[j],那么就有f[i][j]=f[i+1][j-1],因为第i个和第j个字符相等就意味着把序列第i~j变成回文串只需要把i+1~j-1变成回文串即可,所需代价是相同的。
(2)s[i]!=s[j],这个时候我们又需要进行分类讨论,我们可以知道的是这时候i~j一定不是回文序列,为了使其变成回文序列,我们有以下几种做法:要么在第j个字符后面添加一个与第i个字符相同的字符,或者直接删除第i个字符,或者在i前面添加一个与第j个字符相同的字符,再或者直接删除第j个字符,我们一个一个进行分析
1.在第j个字符后面添加一个与第i个字符相同的字符,那么这个时候f[i][j]=f[i+1][j]+添加第i个字符的代价,这个时候我们就默认第i+1~j个字符已经是回文串了
2.直接删除第i个字符,那么这个时候f[i][j]=f[i+1][j]+删除第i个字符的代价,这个时候我们也是默认第i+1~j个字符已经是回文串了
3.在i前面添加一个与第j个字符相同的字符,这个时候f[i][j]=f[i][j-1]+添加第j个字符的代价,这种情况是默认第i~j-1个字符是回文串
4.直接删除第j个字符,这个时候f[i][j]=f[i][j-1]+删除第j个字符的代价,这种情况也是默认第i~j-1个字符是回文串
我们只需要对上述几种情况取一个最小值即可,我们发现情况1和2都是利用的f[i+1][j],不同点仅在于是添加第i个字符还是删除第i个字符,所以我们可以直接在读入时存下修改第i个字符的最小代价,也就是在添加和删除之间取一个最小值,情况3和4同理
下面说一下初始化:由于是求最小值,所以肯定要把所有的f[i][j]初始化为正无穷大,然后再对一些特殊值进行修改,比如f[i][i]=0,f[i][i-1]=0,f[i][i]等于0很好理解,因为单个字符就是回文串,根本不需要修改,所以代价为0,但是f[i][i-1]等于0该如何理解呢?其实不难发现这是一种无效字符,也就是说他只在一开始的答案更新中起作用,换句话说就是他只在长度为2的区间更新中起作用,这是由转移方程决定的,因为当s[i]==s[j]时有f[i][j]=f[i+1][j-1],若j=i+1,那么f[i][j]=f[i+1][i],实际上这个应该等于0,所以我们只需要把f[i][i-1]初始化为0即可
下面是代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=2e3+10;
int f[N][N];//f[i][j]代表将原序列中第i个字符到第j个字符变为回文序列的最小代价
int mp[N];//mp[a]代表删除a和添加a所需代价的较小值
int main()
{
int n,l;
cin>>n>>l;
string s;
cin>>s;
s=" "+s;//让s的有效字符从1开始
memset(f,0x3f,sizeof f);
char op[5];
for(int i=1;i<=n;i++)
{
scanf("%s",op);
int x,y;
scanf("%d%d",&x,&y);
mp[op[0]]=min(x,y);
}
for(int i=1;i<=l;i++)
f[i][i-1]=f[i][i]=0;//无效字符串和单个字符的代价都是0
for(int len=2;len<=l;len++)
for(int i=1;i+len-1<=l;i++)
{
int j=i+len-1;
if(s[i]==s[j]) f[i][j]=f[i+1][j-1];
f[i][j]=min(f[i][j],f[i+1][j]+mp[s[i]]);
f[i][j]=min(f[i][j],f[i][j-1]+mp[s[j]]);
}
cout<<f[1][l];
return 0;
}