#luoguP2758. 编辑距离

编辑距离

你好,同学。针对你在二维 DP 转移来源上的薄弱点,我们今天重点攻克 P2758 编辑距离。这道题是字符串处理类 DP 的教科书级题目,它能完美解决你对“左、上、左上”来源的选择困惑。


第一部分:NOI 规范题面

【题目描述】

AABB 是两个字符串。我们要用最少的字符操作次数,将字符串 AA 转换为字符串 BB。字符操作共有三种:

  1. 删除一个字符;
  2. 插入一个字符;
  3. 将一个字符改为另一个字符。

A,BA, B 均只包含小写字母。

【输入格式】

共两行,每行一个字符串,分别表示 AABB

【输出格式】

一个整数,表示最少操作次数。

【输入输出样例 #1】

输入 #1

sfdqxbw
gfdgw

输出 #1

4

【数据规模与约定】

对于 100%100\% 的数据,1A,B20001 \le |A|, |B| \le 2000。 时间限制:1.0s1.0 \text{s},内存限制:128MB128 \text{MB}


第二部分:预备知识点

  1. 二维状态定义:理解 dp[i][j] 代表 A 串前 ii 个字符变换到 B 串前 jj 个字符的最小代价。
  2. 边界处理:当其中一个字符串为空时,变换代价即为另一个字符串的长度。
  3. 多来源决策(Min 决策):在三种物理操作(增、删、改)中寻找最优解。
  4. 1-based Indexing:在处理字符串 DP 时,通常将下标从 1 开始,保留第 0 行/列作为边界。

第三部分:启发式思维引导(草稿纸上的推理)

请拿出一张草稿纸,跟我一起画出这个思维过程:

Step 1: 建立坐标系

画一个表格,纵轴代表字符串 AA(长度 nn),横轴代表字符串 BB(长度 mm)。 提问:如果 AA 是空串(长度为 0),变成长度为 jjBB 串需要几步? 行动:在第一行填入 0,1,2,,j0, 1, 2, \dots, j(全是插入操作)。同理,第一列填入 0,1,2,,i0, 1, 2, \dots, i(全是删除操作)。

Step 2: 核心决策分析

假设我们正在处理 dp[i][j](即 A[i]A[i] 要匹配 B[j]B[j]),此时纸上有三个相邻的状态:

  1. 左上方 dp[i-1][j-1](双向同步):

    • 如果 A[i]==B[j]A[i] == B[j],不需要任何操作。状态直接继承:dp[i][j] = dp[i-1][j-1]
    • 如果 A[i]B[j]A[i] \neq B[j],进行“替换”操作。代价:dp[i-1][j-1] + 1
  2. 上方 dp[i-1][j](只删 A):

    • 代表 AA 的前 i1i-1 个字符已经匹配了 BB 的前 jj 个。现在多出了 A[i]A[i],必须删除它。代价:dp[i-1][j] + 1
  3. 左方 dp[i][j-1](只加 A):

    • 代表 AA 的前 ii 个已经匹配了 BB 的前 j1j-1 个。现在 B 还多一个 B[j]B[j],必须在 A 后面插入一个。代价:dp[i][j-1] + 1

Step 3: 复杂度思考

  • 空间2000×20002000 \times 2000int 数组约占用 16MB16\text{MB},远小于 128MB128\text{MB}
  • 时间:双重循环,复杂度 O(NM)O(NM),约 4×1064 \times 10^6 次运算,在 1s1\text{s} 内绰绰有余。

第四部分:逻辑流程图 (Mermaid)

为了避免特殊符号报错,我使用了文字描述逻辑。

graph TD
    Start("开始 (Standard C++14)") --> ReadStr("读取 A 串和 B 串")
    ReadStr --> GetLen("获取长度 n 和 m")
    
    subgraph Initialization ["初始化阶段"]
        InitCol("循环 i 从 0 到 n: dp_i_0 = i (删除操作)")
        InitRow("循环 j 从 0 到 m: dp_0_j = j (插入操作)")
    end

    GetLen --> InitCol
    InitCol --> InitRow
    
    subgraph DP_Loop ["状态转移嵌套循环"]
        OuterLoop("外层: i 从 1 到 n")
        InnerLoop("内层: j 从 1 到 m")
        CheckMatch{"判断 A_i-1 是否等于 B_j-1"}
        
        Source1("相等: dp_i_j = dp_i-1_j-1")
        Source2("不等: dp_i_j = min(左, 上, 左上) + 1")
    end

    InitRow --> OuterLoop
    OuterLoop --> InnerLoop
    InnerLoop --> CheckMatch
    
    CheckMatch -- "是" --> Source1
    CheckMatch -- "否" --> Source2
    
    Source1 --> NextJ("j 循环结束?")
    Source2 --> NextJ
    
    NextJ -- "否" --> InnerLoop
    NextJ -- "是" --> NextI("i 循环结束?")
    
    NextI -- "否" --> OuterLoop
    NextI -- "是" --> Output("输出 dp_n_m")
    
    Output --> End("结束")

第五部分:读题关键词与技巧总结

在做 NOI 级别的 DP 题时,通过关键词快速定位:

  1. “最少操作次数”:典型的最优性问题,考虑 DP。
  2. “字符串 A 转换为 B”:双序列匹配 DP。
  3. “插入、删除、修改”
    • 插入/删除:对应 DP 表的水平或垂直移动(只消耗一个序列)。
    • 修改/匹配:对应 DP 表的斜对角线移动(同时消耗两个序列)。
  4. 数据范围 20002000
    • 这是暗示 O(N2)O(N^2) 算法的标志性数字。如果范围是 10510^5,则需要考虑贪心 + 二分或其它高级优化。

教练提示:你之前错在字符相等时加了 +1。记住:DP 转移的本质是“物理动作的代价”。如果两个字符已经相等,你“不需要做任何动作”,所以代价是 +0。请在做这题时,务必在草稿纸上模拟相等字符的跳转逻辑!加油!


参考:

播客讲解(Variant 1: Edit Distance)