编辑距离问题

 先设A的长度为LA,B的长度为LB,并且第一个字符的编号为1。

 这种类型的dp,经常都是以首尾字符作为突破口的。
我们来看一下A[1],由于最后B是要变成跟A一样的,所以,为了获得一个字符来跟A[1]配对,必然满足其中一个情况:(1) 我们要么插一个字符x(x等于A[1])到B里面去跟A[1]配对,或者使用B本身就有的某个字符B[k](B[k]等于A[1])来跟A[1]配对。

先看情况一,就是 我们插入一个x到B里面去跟A[1]配对。
显然,这个x最简单就是插入到B的最前面,也就是B[1]的前面,然后我们要做的就是 将 B[1....LB] 和 A[2....LA] 变成一样。成功转移!
可是,除了插到B的最前面以外,我们还可以把x插入到B的其他位置啊,不需要考虑一下吗?
这个疑问不是多余的,所以现在就来证明一下:只考虑插入到B的最前面,不考虑插入到B的其他部分,也不会影响最后得到的最优解。
假设存在一个最优方案,是把x插到B[1]后面的某个位置的,譬如说插入到B[3]的前面(B[2]的后面)。那么,当我们插入x到B[3]前面以后,我们要做的就是 把B[1..2]删掉,把B[3....LB] 变成和A[2...LA]一样。我们可以对这个方案实施剪切粘贴,也就是说,当我们把x插入到B[1]前面之后,接下来,我们也可以一样地通过 把B[1..2]删掉,把B[3....LB] 变成和A[2...LA]一样 来实现我们的最终目的——把B变成和A一样!所以,就算我们只考虑把x插入到B[1]的前面,我们最后也肯定不会错过最优解!

再看情况二,就是 我们尝试在B里面找某个B[k]来跟A[1]配对。
最简单的情况就是,如果B[1]恰好等于A[1],那么我们要做的就只是 把B[2...LB]变成和A[2...LA]一样。转移成功。
可是,这里还有个疑问,就算A[1]恰好等于B[1], 但是我们前面说,我们是要找某个B[k]来跟A[1]配对的,可是现在只考虑了用B[1],譬如说,如果B[4]也恰好等于A[1],难道不需要考虑吗?
重施故技,还是使用剪切粘贴的思路来证明:只考虑用B[1]来配对A[1],不考虑其他的B[j],是不会影响最后的最优解的。
在B[1]等于A[1]的前提下,假设存在一个最佳方案,并且该最佳方案使用B[4]来跟A[1]配对,那么就意味着,我们要 把B[1..3]删掉,然后把 B[5...LB]变成和A[2....LA]一样。考虑一下,如果我们使用B[1]来跟A[1]配对,接下来,我们也可以 把B[2....4]删掉,然后把B[5...LB]变成和A[2....LA]一样。注意看,这样做付出的代价,跟 “把B[1..3]删掉,然后把 B[5...LB]变成和A[2....LA]一样” 是一样的!也就是说,当B[1]等于A[1]的时候,就算我们只考虑用B[1]来跟A[1]配对,也肯定不会错过最优解的!

继续看,如果B[1]不等于A[1]呢?那我们就只能找另外一个B[j]来跟A[1]配对了,也就意味着我们必须把B[1]删掉了,然后我们要做的就是 把B[2...LB] 变成和A[1....LA] 一样。转移成功。



通过以上的分析,状态转移的思路就全部出来了。

设f(i,j) 表示 将B[j..LB] 变成 A[i..LA]一样的最优方案的代价,那么我们有:
f(i,j) 为
(1)1 + f(i+1,j)
(2)1 + f(i+1,j+1) ,前提是B[j] == A[i]
(3)1 + f(i,j+1),前提是 B[j] != A[i]
取这三者里的最小者


ps,所谓剪切粘贴技术,就是有时候,我们为了解题,需要一些题目没有给出的额外的强化条件或者约束,于是,我们人为地把这些我们需要的条件加上去,然后,我们要做的就是尝试证明,这些我们自己加上去的东西,不会影响最后的问题答案,不然的话,我们是不能加上这些东西的。常用的手段是,我们可以先假设存在一个原问题的最后答案Ans,然后我们只需要证明,我们加上这些额外条件以后得到的答案,跟Ans是一样的!这个技巧在贪心和DP里面用得尤其多,譬如说,单机调度问题里,就是用了这种思路来消除顺序,消除了顺序,一样不影响最后的答案,并且也让dp顺利进行。

 ========================================================================

今天写代码的时候发现看漏了一种操作,那就是修改某个字符。

 

所以重新写一下。不过这次写得简单些。

对于A[1]来说,要么我们插入一个x等于A[1]到B里面,这种情况上面已经讨论过,不然的话,要么我们用B[1]来跟A[1]配对,要么不用B[1],

如果我们用B[1]来跟A[1]配对,又分两种情况,如果B[1]本身就等于A[1],那么不用修改B[1],不然的话就要修改B[1]为A[1]

如果我们不用B[1]来跟A[1]配对,就意味着B[1]必须被删掉。

 

综上,转移方程为

f(i, j) =
f(i + 1, j) + 1            插入A[1]到B的情况
f(i + 1, j + 1)                      用B[1]来跟A[1]配对,而且B[1]==A[1]
f(i + 1, j + 1) +1                 用B[1]来跟A[1]配对,而且B[1]!=A[1]
f(i, j + 1) + 1                      不用B[1]来跟A[1]配对

 

 
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
char A[100];
char B[100];
int LA, LB;
int f[105][105];
int dp(int i, int j) {
  int & ans = f[i][j];
  if (ans != -1) return ans;
  if (i == LA + 1 && j == LB + 1) return ans = 0;
  if (i == LA + 1) return ans = LB - j + 1;
  if (j == LB + 1) return ans = LA - i + 1;
  ans = dp(i + 1, j) + 1;
  ans = min(ans, dp(i + 1, j + 1) + (A[i] == B[j] ? 0 : 1) );
  ans = min(ans, dp(i, j + 1) + 1);
  return ans;
}

int main() {
  scanf("%s %s", A + 1, B + 1);
  LA = strlen(A + 1);
  LB = strlen(B + 1);
  for (int i = 0; i < 105; ++i)
    for (int j = 0; j < 105; ++j)
      f[i][j] = -1;
  printf("%d\n", dp(1, 1));
  return 0;
}
 

你可能感兴趣的:(dp)