20200301集训练习题
P2758 编辑距离
这显然是一道DP题,我们定义 dp[i][j] 为字符串 a 匹配到 i ,字符串 b 匹配到 j 时需要用的最小操作次数,状态就定义完了。
我们来写状态转移方程,它给了三个操作,因此我们根据它来推式子
1、删除一个字符;
2、插入一个字符;
3、将一个字符改为另一个字符;我们显然要修改成与对应字符相同的字符。
4、如果自然就匹配上了,那么直接转移。
核心代码:
memset(dp,127,sizeof(dp));
dp[0][0] = 0;
for(int i = 0;i <= lb;i ++){
for(int j = 0;j <= la;j ++){
if(j) dp[i][j] = dp[i][j-1]+1;
if(i) dp[i][j] = min(dp[i][j],dp[i-1][j] + 1);
if(i&&j) dp[i][j] = min(dp[i][j],dp[i-1][j-1]+1);
if(i&&j) if(a[j] == b[i]) dp[i][j] = min(dp[i][j],dp[i-1][j-1]);
}
}
对于Beer Game我们发现就是多了一些数字,要求这些数字必须要全部转化回字符,其余处理与洛谷上的这道题几乎一样。
我们可以首先将所有数字打开来,预处理出没有数字的字符串,新加的字符用特殊标识占位。然后我们将上一题的第四个转移方程中的 == 处理一下,使得不仅两边相等时直接转移,任意一边是新添加的特殊字符时也直接转移。
虽然题目说我们可以在两个字符串上进行添加和删除操作,但是想一想,在 a 上添加等价于在 b 上删除,同理在 a 上删除等价于在 b 上添加,因此我们可以只修改 b 串,得到的答案仍然是正解。
核心代码:
memset(dp,76,sizeof(dp));
dp[0][0] = 0;
for(int i = 0;i <= lb;i ++){
for(int j = 0;j <= la;j ++){
if(j) dp[i][j] = dp[i][j-1]+1;
if(i) dp[i][j] = min(dp[i][j],dp[i-1][j] + 1);
if(i&&j) dp[i][j] = min(dp[i][j],dp[i-1][j-1]+2);
if(i&&j) if(s1[j] == s2[i] || (s1[j]==0 || s2[i]==0)) dp[i][j] = min(dp[i][j],dp[i-1][j-1]);
}
}
可以看出,两份代码的核心内容基本一致。
完整代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
using namespace std;
const int N=2e4+7,M=2e3+7;
char a[N],b[M];
int la,lb,cnta,cntb;
int dp[M][N];
int s1[N],s2[N];
int tmp = 0;
int main()
{
scanf("%s %s",a+1,b+1);
la = strlen(a+1);lb = strlen(b+1);
for(int i = 1;i <= la;i ++){
if(isdigit(a[i])){
tmp ++;
for(int j = '1';j <= a[i];j ++) s1[++cnta] = 0;
}
else s1[++cnta] = a[i];
}
for(int i = 1;i <= lb;i ++){
if(isdigit(b[i])){
tmp ++;
for(int j = '1';j <= b[i];j ++) s2[++cntb] = 0;
}
else s2[++cntb] = b[i];
}
la = cnta,lb = cntb;
memset(dp,76,sizeof(dp));
dp[0][0] = 0;
for(int i = 0;i <= lb;i ++){
for(int j = 0;j <= la;j ++){
if(j) dp[i][j] = dp[i][j-1]+1;
if(i) dp[i][j] = min(dp[i][j],dp[i-1][j] + 1);
if(i&&j) dp[i][j] = min(dp[i][j],dp[i-1][j-1]+2);
if(i&&j) if(s1[j] == s2[i] || (s1[j]==0 || s2[i]==0)) dp[i][j] = min(dp[i][j],dp[i-1][j-1]);
}
}
printf("%d",dp[lb][la]+tmp);
return 0;
}