题目链接:
UVALive 6697 - Homework Evaluation
题意:
给出两个字符串,用第二个去匹配第一个字符串,第二个字符串中的字母可以看成:新插入的,有丢失的,正确匹配的,不正确的。
- 正确的+8分
- 错误的-5分
- 插入或删除一个字母-3分
- 连续插入或删除额外扣除-4分
例如:
Whatagreatisl/andtovisit gr//tisllamd/表示插入或删除,
应用上面的规则可以得到的分数是:
正确匹配:8∗8错误匹配:5∗1额外插入和删除:3∗3连续插入和删除:4∗2最终得分:64−5−9−8=42
现在给出两个字符串(第一个长度不大于100,第二个长度不大于50),问最优得分是多少?
分析:
dp思路
我们可以发现第二个字符串的每个字母要么和第一个字符串的某个字母匹配,要么看成是插在第一个字符串的某个字母前面。
- 用
dp[0][i][j]
表示第二个字符串的第
i
个字母和第一个字符串的第
j 个字母匹配时,前 i 个字母的得分 - 用
dp[1][i][j] 表示第二个字符串的第 i 个字母插在第一个字符串的第j 个字母前面时,前 i 个字母的得分
考虑状态转移方程。设字符串的下标都是从1开始,第一个字符串为
- 考虑初始化。
memset(dp, 0, sizeof(dp));
if( i > 1) dp[0][i][1] = -3 * (i - 1) - 4;
if(ss[i] == s[1]) dp[0][i][1] += 8;
else dp[0][i][1] -= 5;
dp[1][i][1] = -i * 3 - 4;
dp[0][i][0] = dp[1][i][0] = -inf;
- 当第二个字符串的第
i
和第一个字符串的第
j 个匹配时,遍历第一个字符串的前 j 个字母,考虑第二个字符串的第i−1 个字母的匹配位置 k ,先初始化dp[0][i][j]=−inf
对于 dp[0][i][j] ,也就是恰好 第i个和第j个匹配 。
- 如果
k<j−1
,如果第
i−1
个也和第
k
个恰好匹配(从
dp[0][i−1][k] 转移),那么就相当于在第 i 个和第i−1 个之间连续删除了 (j−k−1)个字母 ,所以dp[0][i][j]=max(dp[0][i][j],dp[0][i−1][k]−3∗(j−k−1)−4);如果第 i−1 个插入在第 k 个之前(从dp[1][i−1][k] 转移),那么就相当于在第 i 个和第i−1 个之间连续删除了 j−k个 ,所以有:dp[0][i][j]=max(dp[0][i][j],dp[1][i−1][k]−3∗(j−k)−4); - 如果
k=j−1
,同样的道理可得:
dp[0][i][j]=max(dp[0][i][j],dp[0][i−1][k]);//既不插入也不删除dp[0][i][j]=max(dp[0][i][j],dp[1][i−1][k]−3∗(j−k)−4);//j−k=1,正好也相当于插入1个
对于 dp[1][i][j] ,也就是恰好 第i个插在第j个 之前。同样的道理考虑状态转移。
对于第 i−1 个和第 j 个匹配或者插入在第j 个之前的情况也是需要考虑的。
- 如果
k<j−1
,如果第
i−1
个也和第
k
个恰好匹配(从
dp[0][i][j] = max(dp[0][i][j], dp[1][i - 1][j]);
if(ss[i] == s[j]) dp[0][i][j] += 8;
else dp[0][i][j] -= 5;
if(i > 1) dp[1][i][j] = max(dp[1][i][j], dp[1][i - 1][j] - 3);
最后扫一下第二个字符串的最后一个字母在第一个字符串中的匹配位置,结果取最右。别忘了第二字符串的最后一个字母可以看成插入在第一个字符串的最后一个字母后面的情况。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <climits>
#include <cmath>
#include <ctime>
#include <cassert>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
using namespace std;
typedef long long ll;
const int MAX_N = 110;
int T;
char s[MAX_N], ss[MAX_N];
int dp[2][MAX_N][MAX_N];
int main()
{
scanf("%d", &T);
while(T--) {
scanf("%s%s", s + 1, ss + 1);
int len1 = strlen(s + 1) + 1, len2 = strlen(ss + 1) + 1;
if(len1 == len2 && len1 == 2) {
if(s[1] == ss[1]) printf("8\n");
else printf("-5\n");
continue;
}
memset(dp, 0, sizeof(dp));
for(int i = 1; i < len2; ++i) {
if( i > 1) dp[0][i][1] = -3 * (i - 1) - 4;
if(ss[i] == s[1]) dp[0][i][1] += 8;
else dp[0][i][1] -= 5;
dp[1][i][1] = -i * 3 - 4;
dp[0][i][0] = dp[1][i][0] = INT_MIN / 2;
for(int j = 2; j < len1 + 1; ++j) {
dp[0][i][j] = dp[1][i][j] = INT_MIN;
for(int k = 0; k < j; ++k) {
if(k < j - 1) dp[0][i][j] = max(dp[0][i][j], dp[0][i - 1][k] - 3 * (j - k - 1) - 4);
else dp[0][i][j] = max(dp[0][i][j], dp[0][i - 1][k]);
dp[0][i][j] = max(dp[0][i][j], dp[1][i - 1][k] - 3 * (j - k) - 4);
if(k < j - 1) dp[1][i][j] = max(dp[1][i][j], dp[0][i - 1][k] - 3 * (j - k - 1) - 11);
else dp[1][i][j] = max(dp[1][i][j], dp[0][i - 1][k] - 7);
dp[1][i][j] = max(dp[1][i][j], dp[1][i - 1][k] - 3 * (j - k) - 11);
}
dp[0][i][j] = max(dp[0][i][j], dp[1][i - 1][j]);
if(ss[i] == s[j]) dp[0][i][j] += 8;
else dp[0][i][j] -= 5;
if(i > 1) dp[1][i][j] = max(dp[1][i][j], dp[1][i - 1][j] - 3);
}
}
int ans = INT_MIN, n = len2 - 1;
for(int i = 1; i < len1; ++i) {
ans = max(ans, dp[0][n][i]);
ans = max(ans, dp[1][n][i]);
}
ans = max(ans, dp[1][n][len1]);
printf("%d\n", ans);
}
return 0;
}