双倍经验:P2758。
令 \(dp_{i,j}\) 表示 \(s\) 前 \(i\) 个字符要变成 \(t\) 前 \(j\) 个字符所需的最少移动次数。
答案即为 \(dp_{\lvert s \rvert,\lvert t \rvert}\)。
显然有初始状态 \(dp_{i,0}=dp_{0,i}=i\)。
因为我们只可能从添、删、替三种操作转移而来,
于是有转移方程:
- 添:
- 删:
- 替:
三者取 \(\min\) 即可。
关于输出,我们定义递归函数 \(\operatorname{print}(x,y)\)。
边界:\(x \le 0,y \le 0\)。
接着我们分别从上述三种操作中寻找路径。
但是,我们考虑到我们输出的字符一定是目标字符串 \(t\) 中的字符。
因此我们考虑将对 \(s\) 的添、删、替操作转化为对 \(t\) 的操作。
-
添:
若 \(x,y\) 满足 \(x>0,dp_{x,y}=dp_{x-1,y}+1\),
则执行 \(\operatorname{print}(x-1,y)\),并输出 \(\texttt{DELETE} \ y+1\)(\(s\) 增加第 \(x\) 字符与 \(t\) 删去第 \(y+1\) 字符等价)。
-
删:
若 \(x,y\) 满足 \(y>0,dp_{x,y}=dp_{x,y-1}+1\),
则执行 \(\operatorname{print}(x,y-1)\),并输出 \(\texttt{INSERT} \ y\)(\(s\) 删除第 \(x\) 字符与 \(t\) 添加第 \(y\) 字符等价)。
-
替:
若 \(x,y\) 满足 \(x>0,y>0,dp_{x,y}=dp_{x-1,y-1}+1\),
则执行 \(\operatorname{print}(x-1,y-1)\),并输出 \(\texttt{REPLACE} \ y\)(\(s\) 替换第 \(x\) 字符与 \(t\) 替换第 \(y\) 字符等价)。
-
否则:
执行 \(\operatorname{print}(x-1,y-1)\)(即退回一个字符)。
然后这题就做完了。
code
#include<bits/stdc++.h>
using namespace std;
string A,B;
int n,m,dp[2031][2031];
void print(int x,int y){
if(x<=0&&y<=0) return;
if(x>=1&&dp[x][y]==dp[x-1][y]+1) print(x-1,y),cout<<"DELETE "<<y+1<<'\n';
else if(y>=1&&dp[x][y]==dp[x][y-1]+1) print(x,y-1),cout<<"INSERT "<<y<<' '<<B[y]<<'\n';
else if(x>=1&&y>=1&&dp[x][y]==dp[x-1][y-1]+1) print(x-1,y-1),cout<<"REPLACE "<<y<<' '<<B[y]<<'\n';
else print(x-1,y-1);
}
int main(){
getline(cin,A); getline(cin,B);
n=A.length(),m=B.length();
A='#'+A,B='#'+B;
for(int i=1;i<=n;i++) dp[i][0]=i;
for(int i=1;i<=m;i++) dp[0][i]=i;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
dp[i][j]=min(min(dp[i-1][j]+1,dp[i][j-1]+1),dp[i-1][j-1]+(A[i]!=B[j]));
cout<<dp[n][m]<<'\n';
print(n,m);
return 0;
}