原题:
There are two strings A and B with equal length. Both strings are made up of lower case letters. Now you have a powerful string painter. With the help of the painter, you can change a segment of characters of a string to any other character you want. That is, after using the painter, the segment is made up of only one kind of character. Now your task is to change A to B using string painter. What’s the minimum number of operations?
Input
Input contains multiple cases. Each case consists of two lines:
The first line contains string A.
The second line contains string B.
The length of both strings will not be greater than 100.
Output
A single line contains one integer representing the answer.
Sample Input
zzzzzfzzzzz
abcdefedcba
abababababab
cdcdcdcdcdcd
Sample Output
6
7
中文:
给你两个字符串a和字符串b,你有一个刷子,一次可以将一个连续区间的字符串刷成同一种字符,限制问你最少刷多少次可以将字符串a刷成b。
例如第一个给的样例
zzzzzfzzzzz
abcdefedcba
要求将字符串zzzzzfzzzzz刷成abcdefedcba
第一步可以刷成这样
aaaaaaaaaaa
第二步
abbbbbbbbba
第三步
abcccccccba
第四步
abcdddddcba
第五步
abcdeeedcba
第六步
abcdefedcba
代码:
#include
using namespace std;
const int maxn = 100 + 10;
char s1[maxn],s2[maxn];
int dp[maxn][maxn];
int ans[maxn];
int main()
{
ios::sync_with_stdio(false);
while(cin>>&s1[1])
{
cin>>&s2[1];
memset(dp,0,sizeof(dp));
memset(ans,0,sizeof(ans));
int len = strlen(&s1[1]);
for(int R = 1;R<=len ;R++)
{
for(int L = R; L>=1; L--)
{
dp[L][R] = dp[L+1][R]+1;
for(int k = L + 1; k<=R ;k++)
{
if(s2[L] == s2[k])
dp[L][R] = min(dp[L][R], dp[L+1][k] + dp[k+1][R]);
}
}
}
for(int i = 1;i<=len;i++)
{
ans[i]=dp[1][i];
}
for(int i = 1;i<=len;i++)
{
if(s1[i] == s2[i])
ans[i] = ans[i-1];
else
{
for(int j=1;j<=i;j++)
ans[i]=min(ans[i],dp[j+1][i] + ans[j]);
}
}
cout<<ans[len]<<endl;
}
return 0;
}
解答:
此题目是我做过精妙的区间dp问题之一,看别人代码看了半天才想明白-_-|| 。
数据上来说,字符串长度最多只有100。这意味着要求时间复杂度要在O(n^3)以内完成。
按照给的样例,手动模拟一下不难发现,如果单纯利用区间dp的模型来考虑,每次进行状态转移的的方法按照区间来考虑,状态转移方程如下:
d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ i ] [ x − 1 ] + p a i n t ( x , y ) + d p [ y + 1 ] [ j ] ) dp[i][j] = min(dp[i][j],dp[i][x-1] + paint(x,y) + dp[y+1][j] ) dp[i][j]=min(dp[i][j],dp[i][x−1]+paint(x,y)+dp[y+1][j])
其中paint(x,y)表示把a字符串涂成b字符串,这里有个问题,paint(x,y)函数怎么实现?
如果要将区间[x,y]涂成字符串b对应的字符串,那么如何处理这个区间才能使paint函数最优?
这样又回到了原来的问题。
所以,此题不好在区间上考虑。
考虑将一个空字符串涂抹成字符串b所需要的最少涂抹次数,设置状态dp[i][j]表示将空字符串区间[i,j]涂抹成字符串b区间[i,j]的字符串所需要的最少涂抹次数:
状态转移方程表示为:
初始 d p [ i ] [ j ] = d p [ i + 1 ] [ j ] + 1 dp[i][j]= dp[i+1][j] + 1 dp[i][j]=dp[i+1][j]+1表示可以将第i个字符单独涂成字符串b[i]对应的字符
如果在字符串b中有b[i]==b[k],其中k∈[i+1,j]
d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ i + 1 ] [ k ] + d p [ k + 1 ] [ j ] ) dp[i][j] = min(dp[i][j],dp[i+1][k] + dp[k+1][j]) dp[i][j]=min(dp[i][j],dp[i+1][k]+dp[k+1][j])
表示如果b[i]字符与b[k]字符相同,那么可以先将区间[i,k]一次涂抹成字符b[i]的形式
这里有个问题,如果在 b [ i ] = = b [ k ] b[i]==b[k] b[i]==b[k]时 d p [ i + 1 ] [ k ] dp[i+1][k] dp[i+1][k]表示将区间[i,k]涂抹成字符串b[k],为什么不可以表达成下面这样的形式?
d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ i + 1 ] [ k ] + 1 + d p [ k + 1 ] [ j ] ) dp[i][j] = min(dp[i][j],dp[i+1][k]+1 + dp[k+1][j]) dp[i][j]=min(dp[i][j],dp[i+1][k]+1+dp[k+1][j])
或
d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ i + 1 ] [ k − 1 ] + 1 + d p [ k + 1 ] [ j ] ) dp[i][j] = min(dp[i][j],dp[i+1][k-1] + 1 + dp[k+1][j]) dp[i][j]=min(dp[i][j],dp[i+1][k−1]+1+dp[k+1][j])
从代码中可以看到,首先枚举右区间,然后再遍历左区间,这里右区间是固定的,即使再寻找子状态来构造当前状态的最优解也是以最右边的字符作为参照标准。
第一个状态转移方程不能+1的原因在于,如果 b [ i ] = = b [ k ] b[i]==b[k] b[i]==b[k]那么你要涂抹区间[i,k]时,也就是在处理第k个字符的时候就已经将整个区间涂抹过了
第二个方程不能写成 d p [ i + 1 ] [ k − 1 ] + 1 dp[i+1][k-1] + 1 dp[i+1][k−1]+1形式,因为该形式只表示你考虑第i个字符与第k个字符的涂抹,而没有将区间表示出来。