UVALive - 4394 String painter DP

题目大意:给出两个字符串,可以对将第一个字符串进行操作,操作的过程是将第一个字符串中的一个连续的子串换成同一个字母,问至少需要多少次操作才能使第一个字符串变成第二个字符串

解题思路:这里两种解法:

第一个解法(比较繁琐):设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;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值