题目大意:给出两个字符串,可以对将第一个字符串进行操作,操作的过程是将第一个字符串中的一个连续的子串换成同一个字母,问至少需要多少次操作才能使第一个字符串变成第二个字符串
解题思路:这里两种解法:
第一个解法(比较繁琐):设dp[i]为扫描到字符串的第i个字符时需要进行操作的最小次数。这里有两种情况
1.str1[i] == str2[i],这样的话就不需要对第i个进行操作了,直接dp[i] = (i == 0 ? 0 : dp[i-1])
2.str1[i] != str2[i],从左到右扫描,如果str2[j] == str2[i],就从这个j处进行切割,将其分成两个部分,[0,j-1]和[j,i],这样的话dp[i] = min(dp[i],dp[j-1]+statu[j+1][i-1][str2[j]-'a']+1),还要考虑到j是0的情况,当j == 0,dp[i] = min(dp[i],statu[j+1][i-1][str2[j]-'a']+1)
这里解释一下这个statu数组和后面的1的意思,statu[i][j][k]表示将第一个字符串从第i个字符到第j个的字符都为k的子串变成相应的第二个字符串的子串需要操作的最少次数,为什么会从第i个字符到第j个字符都是字符k呢,因为头尾相同的情况下,可以将中间部分变成和头尾相同的字符,这样可以减少一次操作,所以后面要再statu后面加上了1,表示进行了一个前面所说的操作了,具体看代码,写得比较急,如果写错的话请见谅
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define maxn 110
#define INF 0x3f3f3f3f
char str1[maxn], str2[maxn];
int statu[maxn][maxn][30], dp[maxn];
int DP(int start, int end, int c) {
if(start > end)
return 0;
if(statu[start][end][c] != INF)
return statu[start][end][c];
if(start == end) {
if(str2[start] - 'a' == c)
statu[start][end][c] = 0;
else
statu[start][end][c] = 1;
return statu[start][end][c];
}
for(int i = start; i <= end - 1; i++)
statu[start][end][c] = min(statu[start][end][c],DP(start,i,c)+DP(i+1,end,c));
if(str2[start] == str2[end]) {
if(str2[start] - 'a' == c)
statu[start][end][c] = min(statu[start][end][c],statu[start+1][end-1][c]);
else
statu[start][end][c] = min(statu[start][end][c],DP(start+1,end-1,str2[start]-'a')+1);
}
return statu[start][end][c];
}
int main() {
while(scanf("%s%s",str1, str2) != EOF) {
int len = strlen(str2);
memset(statu,0x3f,sizeof(statu));
memset(dp,0x3f,sizeof(dp));
for(int i = 0; i < len; i++) {
if(str1[i] == str2[i]) {
if(i == 0)
dp[i] = 0;
else
dp[i] = dp[i-1];
}
else {
for(int j = i; j >= 0; j--)
if(str2[i] == str2[j]) {
if(j == 0)
dp[i] = min(dp[i],DP(j+1,i-1,str2[i]-'a')+1);
else
dp[i] = min(dp[i],dp[j-1]+DP(j+1,i-1,str2[i]-'a')+1);
}
}
}
printf("%d\n",dp[len-1]);
}
return 0;
}
解法二:(区间DP)可以想将一个空字符串变成第二个字符串,看中间的操作需要进行多少次。用dp[i][j]表示将一个空字符串变成第二个字符串的第i个字符到第j个字符的子串的操作次数。
这里的转换就和上面的差不多,就不详写了,不懂请看代码。用ans[i]表示第一个字符串的前i个字符变成第二个字符串的前i个字符的操作次数。
首先ans[i] = dp[0][i],这是最差的状态
然后如果str1[i] == str2[i], ans[i] = min(ans[i], (i == 0 ? 0:ans[i-1]))
最后再进行区间分段,求出最优的方法,ans[i] = min(ans[i],ans[j] + dp[j+1][i])
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define maxn 110
#define INF 0x3f3f3f3f
int dp[maxn][maxn], ans[maxn];
char str1[maxn], str2[maxn];
void DP(int i, int j) {
if(i == j) {
dp[i][j] = 1;
return ;
}
dp[i][j] = dp[i+1][j] + 1;
for(int k = i + 1; k <= j; k++) {
if(str2[i] == str2[k])
dp[i][j] = min(dp[i][j],dp[i+1][k]+dp[k+1][j]);
}
}
int main() {
while(scanf("%s%s", str1, str2) != EOF) {
int len = strlen(str2);
memset(dp,0,sizeof(dp));
for(int i = 0; i < len; i++)
for(int j = 0; i + j < len; j++)
DP(j,i+j);
for(int i = 0; i < len; i++) {
ans[i] = dp[0][i];
if(str1[i] == str2[i])
ans[i] = min(ans[i],(i == 0 ? 0:ans[i-1]));
for(int j = 0; j < i ; j++)
ans[i] = min(ans[i],ans[j]+dp[j+1][i]);
}
printf("%d\n",ans[len-1]);
}
return 0;
}