題意:給一個長度爲m的字符串s,其中含有n種字母,給出添加和刪除某種字母所需要的代價,你可以通過添加或刪除某些字母來使這個字符串變成迴文串,求使當前這個字符串變成迴文串需要的最少代價是多少。
題解:典型的區間DP,可以通過小區間一點一點的推到大區間。定義dp[i][j]表示把區間(i,j)編爲迴文串所需要的最小代價,那麼我們可以從以下幾個方面來分析。
首先對於一個區間dp[i][j]來說,如果s[i]==s[j],那麼對於這個區間來說,兩端的s[i]和s[j]加上以後不會對原區間dp[i+1][j-1]產生影響,使區間dp[i][j]成爲迴文串的最小代價就是使區間dp[i+1][j-1]的最小代價。此處狀態轉移方程:
dp[i][j]=dp[i+1][j-1];
其次,對於一個區間來講,它可以通過兩個位置推過來,就是它的子區間dp[i+1][j]和dp[i][j-1]。爲什麼是這兩個位置呢?因爲我們每次判斷多一個字母的位置才方便判斷,當判斷它之前,它之前的區間已經被處理過了,所以對於一個迴文串加上一個字母讓它變成新的迴文串有兩種操作,要麼是刪除掉這個字母,要麼是在這個字母所在位置的另一端加上相同的字母,所以只用比較是刪除當前字母的代價小還是添加一個新的代價小。所以此處的轉移方程是:
dp[i][j]=min(dp[i+1][j]+min(add(s[i]),del(s[i])),dp[i][j]);
dp[i][j]=min(dp[i][j-1]+min(add(s[i]),del(s[i])),dp[i][j]);
分析完狀態轉移方程,我們就可以枚舉區間長度和區間起點和終點,對每個區間進行計算,最後我們要求的值就是dp[0][slen-1]。
附上代碼:
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=2010;
char s[maxn];
int dp[maxn][maxn];///dp[i][j]表示把區間[i,j]的字符串變成迴文串所需的最小花費
int num[maxn];
int main()
{
int n,m,x,y;
char c;
scanf("%d%d",&n,&m);
scanf("%s",s);
for(int i=1;i<=n;i++)
{
cin>>c>>x>>y;
num[c]=min(x,y);
}
memset(dp,0,sizeof(dp));
int len=strlen(s);
for(int l=1;l<=len;l++)//枚舉區間長度
for(int i=0,j=l;j<len;i++,j++)//枚舉每一段的開頭和末尾
{
dp[i][j]=0x3f3f3f3f;
if(s[i]==s[j])//頭和尾相等
dp[i][j]=dp[i+1][j-1];//那就和上一段一樣
else {//否則在左右兩個子區間裏進行狀態轉移
dp[i][j]=min(dp[i+1][j]+num[s[i]],dp[i][j]);
dp[i][j]=min(dp[i][j-1]+num[s[j]],dp[i][j]);
}
}
printf("%d\n",dp[0][len-1]);
return 0;
}