题解:洛谷 AT_abc438_b Substring 2

【题目来源】

洛谷:AT_abc438_b Substring 2 - 洛谷

【题目描述】

给你一个整数 \(N\)\(M\) ,一个长度为 \(N\) 的数字字符串 \(S\) 和一个长度为 \(M\) 的数字字符串 \(T\)。这里,数字字符串是指由从 09 的数字组成的字符串。

您可以执行以下操作 \(0\) 次或多次:

  • \(T\) 中选择一个字符,并将所选数字增加 \(1\)。但是,如果所选数字是 9,则将其改为 0

求使 \(T\) 成为 \(S\) 的子串(连续子序列)所需的最少操作次数。

【输入】

输入内容由标准输入法提供,格式如下

\(N\) \(M\)
\(S\)
\(T\)

【输出】

输出使 \(T\) 成为 \(S\) 的子串所需的最少操作数。

【输入样例】

4 2
2025
91

【输出样例】

2

【核心思想】

  1. 问题分析:给定数字字符串 \(S\)(长度 \(N\))和 \(T\)(长度 \(M\)),可以将 \(T\) 中字符循环增加(90),求使 \(T\) 成为 \(S\) 的子串的最少操作次数。这是一个字符串匹配 + 循环距离问题,核心在于枚举 \(S\) 中所有长度为 \(M\) 的子串,计算与 \(T\) 的循环数字距离。

  2. 算法选择

    • 枚举子串 + 循环距离计算:枚举 \(S\) 中所有长度为 \(M\) 的子串起始位置,对每个位置计算与 \(T\) 的逐位循环距离,取最小值
  3. 关键步骤

    • 读入数据\(N\)\(M\)\(S\)\(T\)
    • 字符串前补空格:使下标从 \(1\) 开始
    • 枚举起始位置\(i\)\(1\)\(N-M+1\)):
      • \(res = 0\)
      • 逐位比较(\(j\)\(1\)\(M\)):
        • 计算循环距离:\((S[i+j-1] - T[j] + 10) \bmod 10\)
        • \(res \leftarrow res + \text{距离}\)
      • \(minn = \min(minn, res)\)
    • 输出结果\(minn\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \cdot M)\),枚举 \(N-M+1\) 个起始位置,每个比较 \(M\)
    • 空间复杂度:\(O(N + M)\),存储字符串
  5. 循环距离与枚举匹配的核心思想

    • 循环数字距离:数字 0-9 形成循环环,从 \(a\)\(b\) 的最小操作次数 = \(\min(|a-b|, 10-|a-b|)\)。代码中 (s[i+j-1] - t[j] + 10) % 10 计算的是 \(a \to b\) 的顺时针距离(只增不减),等价于最小距离是因为...

    实际上,(a - b + 10) % 10 计算的是 \((a - b) \bmod 10\),即从 \(b\) 增加到 \(a\) 需要的步数(顺时针)。但题目允许从 \(T\) 变到 \(S\) 的子串,即 \(T[j] \to S[i+j-1]\)。若 \(T[j]\) 需要增加 \(d\) 次变成 \(S[i+j-1]\),则 \(d = (S[i+j-1] - T[j] + 10) \bmod 10\)。这是单向增加的距离,但由于循环性,增加 \(d\) 次等价于减少 \(10-d\) 次,而题目只允许增加操作,所以 \(d\) 就是最小操作次数。

    • 子串枚举的完备性\(T\) 要成为 \(S\) 的子串,必须在 \(S\) 的某个连续位置完全匹配。枚举所有可能的起始位置确保不遗漏最优解
    • 逐位独立的可加性:每位数字的变换相互独立,总操作次数为各位操作次数之和,满足加法原理
    • 前导空格的技巧:字符串前加空格使下标从 \(1\) 开始,避免 \(0\)-based 和 \(1\)-based 的混淆,简化代码逻辑
    • 适用于字符串子串匹配、循环距离、枚举优化类入门问题

【算法标签】

入门 #字符串入门

【代码详解】

#include <bits/stdc++.h>
using namespace std;

int n, m, minn = 1e9;  // n: 字符串s长度,m: 字符串t长度,minn: 最小代价
string s, t;  // s: 主字符串,t: 目标字符串

int main()
{
    // 输入
    cin >> n >> m >> s >> t;

    // 在字符串前添加空格,使下标从1开始
    s = " " + s;
    t = " " + t;

    // 遍历所有可能的起始位置i
    // 在s中找到一个长度为m的子串,与t进行比较
    for (int i = 1; i + m - 1 <= n; i++)
    {
        int res = 0;  // 记录当前起始位置i的代价

        // 比较s的子串s[i...i+m-1]与t[1...m]
        for (int j = 1; j <= m; j++)
        {
            // 计算从s[i+j-1]变成t[j]的最小代价
            // 代价是数字差的绝对值,但由于是循环数字(0-9),要考虑两种方向
            // 公式:min(|a-b|, 10-|a-b|) 简化成 (a-b+10)%10
            res += (s[i + j - 1] - t[j] + 10) % 10;
        }

        // 更新最小代价
        minn = min(minn, res);
    }

    // 输出最小代价
    cout << minn << endl;

    return 0;
}

【运行结果】

4 2
2025
91
2
posted @ 2026-08-27 11:04  团爸讲算法  阅读(6)  评论(0)    收藏  举报