Loading

洛谷 P2758 编辑距离

题目

P2758 编辑距离

一道典型的线性 DP 题。

思路

状态定义

像这种线性动态规划,一种常见的状态定义方法是:用 \(f_i\) 表示\(i\)元素满足要求(只考虑前 \(i\) 个元素)时的最佳答案。

因此我们很自然地想到用 \(f(i,j)\) 表示将 \(A[1..i]\) 转换为 \(B[1..j]\) 所需的最少操作次数,\(f(n,m)\) 就是问题的答案(\(n,m\) 分别表示字符串 \(A,B\) 的长度)。

状态转移

线性 DP 中,状态转移通常只需要考虑最后一点

请设想这样一种场景,\(A[1..i]\) 经过若干次操作被改成了 \(B[1..j]\),且最后一步操作是在 \(A\) 的末尾进行的。我们不由地想到,这最后一步操作只可能是以下三种:

  1. 删除 \(A\) 末尾的字符;
  2. \(A\) 的末尾插入一个字符;
  3. 修改 \(A\) 末尾的字符。

于是我们发现,有三种策略可以把 \(A[1..i]\) 修改为 \(B[1..j]\)

  1. 先把 \(A[1..i-1]\) 变得跟 \(B[1..j]\) 一样,再删除 \(A\) 末尾的字符;
  2. 先把 \(A[1..i]\) 变得跟 \(B[1..j-1]\) 一样,再在 \(A\) 的末尾插入一个字符;
  3. 先把 \(A[1..i-1]\) 变得跟 \(B[1..j-1]\) 一样,再修改 \(A\) 末尾的字符。

状态转移式:

\[f(i,j)=\min \left\{ \begin{aligned} f(i-1,j) & +1 \\ f(i,j-1) & +1 \\ f(i-1,j-1) & + \left\{ \begin{aligned} 0 & ,\textbf{if }A_i=B_j\\ 1 & ,\textbf{if }A_i \neq B_j \end{aligned} \right. \end{aligned} \right. \quad (i>0,j>0) \]

边界条件

观察状态转移式,注意从左往右的下标变化,下标要么不变,要么变小,因此边界就是下标为 \(0\) 的时候:

\[\begin{aligned} f(0,j) & = j \\ f(i,0) & = i \\ f(0,0) & = 0 \\ \end{aligned} \]

递推顺序

由于从左往右下标要么不变,要么变小,因此递推顺序是从小到大枚举 \(i\)\(j\)

在开始递推之前,应先求出所有边界的值。

代码

字符串下标从 1 开始。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int maxn = 2000 + 5;
char A[maxn];
char B[maxn];
int f[maxn][maxn];

int main()
{
#ifdef LOCAL
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif

    scanf("%s%s", A + 1, B + 1);
    int n = strlen(A + 1);
    int m = strlen(B + 1);

    f[0][0] = 0;
    for (int i = 1; i <= n; i++)
        f[i][0] = i;
    for (int j = 1; j <= m; j++)
        f[0][j] = j;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            f[i][j] = min(f[i - 1][j] + 1, min(f[i][j - 1] + 1, f[i - 1][j - 1] + (A[i] == B[j] ? 0 : 1)));

    printf("%d", f[n][m]);

    return 0;
}
posted @ 2021-01-03 21:51  zhb2000  阅读(148)  评论(0编辑  收藏  举报