请你认认真真看完题目并思考,再参考蒟蒻的题解。
思考过程
个人认为,这道题的正解是区间DP吧,当然各路大佬的神奇做法也很好。
好的,这道题如果用区间DP做,应该思考什么呢?
答案非常明确,就是DP的数组表示什么、动态转移方程怎么写。
数组
DP的数组比较容易得到。题目中要求输出的是最少字符操作次数,
DP的题目有个大的特点:题目问什么就设什么。
所以我们可以设一个二维数组f,f[i][j]表示把字符串A的前i个字符变成字符串B的前j个字符所需的最少字符操作次数。
其实,设定数组的经验往往可以通过刷题刷出来。
目标
我们设A的长度为lena,B的长度为lenb。
由数组的定义可知,我们的目标是f[lena][lenb],
即把A全部变成B所需的最少操作数。
动态转移方程
动态转移方程要倒着考虑,要想f[i][j]是由什么推出来的。
由此,我们要分类讨论。
1. 如果A[i]与B[j]相等,就说明让A[i]==B[j]是不用操作的,
所以 f[i][j]=min(f[i][j],f[i-1][j-1])
2. 如果A[i]与B[j]不等,那么使A[i]==B[j]相等需要一步,
f[i][j]可以在f[i-1][j]的基础上插入A[i],需要一次操作;
f[i][j]可以在f[i][j-1]的基础上插入B[j],需要一次操作;
f[i][j]可以在f[i-1][j-1]的基础上把A[i]替换成B[j],需要一次操作。
于是动态转移方程就出来了:
f[i][j]=Min(f[i][j],f[i-1][j-1]+1,f[i-1][j]+1,f[i][j-1]+1);
为了简化码风,可以写成这样:
f[i][j]=Min(f[i][j],f[i-1][j-1],f[i-1][j],f[i][j-1])+1;
好像看起来也没有简洁多少……
初始化
因为要求最小操作次数,所以将f赋值以一个很大的数,
然而,如果仅仅这样,你会发现答案会是一个很大的数。
那是因为我们没有考虑特殊值。
那么有哪些特殊值呢?
就是把一个字符串的前i个字符变成另一个字符串的前1个字符所需的操作数是i。
原因是可以把[2,i]区间内每个数都删除,用了i-1个操作,
再把第一个字符替换为另一个字符串的第一个字符,用了1次操作。
合计i次操作。
1 for(int i=1;i<=lena;i++) 2 f[i][1]=i; 3 for(int i=1;i<=lenb;i++) 4 f[1][i]=i;
Code:
为了让大家方便查看,贴上高清无注释的代码,在过程中我讲解得很详细了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 char a[2005],b[2005]; 4 5 int f[2005][2005]; 6 int lena,lenb; 7 int Min(int a1,int a2,int a3,int a4) 8 { 9 a1=min(a1,a2); 10 a1=min(a1,a3); 11 a1=min(a1,a4); 12 return a1; 13 } 14 int main() 15 { 16 memset(f,0x3f3f3f3f,sizeof(f)); 17 scanf("%s%s",a+1,b+1); 18 19 lena=strlen(a+1); 20 lenb=strlen(b+1); 21 22 for(int i=1;i<=lena;i++) 23 f[i][1]=i; 24 for(int i=1;i<=lenb;i++) 25 f[1][i]=i; 26 for(int i=1;i<=lena;i++) 27 for(int j=1;j<=lenb;j++) 28 29 { 30 if(a[i]==b[j]) 31 32 f[i][j]=min(f[i][j],f[i-1][j-1]); 33 34 else f[i][j]=Min(f[i][j],f[i-1][j-1],f[i-1][j],f[i][j-1])+1; 35 36 } 37 cout<<f[lena][lenb]-1<<endl; 38 return 0; 39 }
总结
要想练好DP就要多刷题,DP算法代码虽然不长,但是非常难想。
这也是DP学不好的OIer不能成为大佬的原因。
DP的题目尽量要自己推动态转移方程。
希望大家能有所收获!