题解:洛谷 AT_abc438_b Substring 2
【题目来源】
洛谷:AT_abc438_b Substring 2 - 洛谷
【题目描述】
给你一个整数 \(N\) 和 \(M\) ,一个长度为 \(N\) 的数字字符串 \(S\) 和一个长度为 \(M\) 的数字字符串 \(T\)。这里,数字字符串是指由从 0 到 9 的数字组成的字符串。
您可以执行以下操作 \(0\) 次或多次:
- 从 \(T\) 中选择一个字符,并将所选数字增加 \(1\)。但是,如果所选数字是
9,则将其改为0。
求使 \(T\) 成为 \(S\) 的子串(连续子序列)所需的最少操作次数。
【输入】
输入内容由标准输入法提供,格式如下
\(N\) \(M\)
\(S\)
\(T\)
【输出】
输出使 \(T\) 成为 \(S\) 的子串所需的最少操作数。
【输入样例】
4 2
2025
91
【输出样例】
2
【核心思想】
-
问题分析:给定数字字符串 \(S\)(长度 \(N\))和 \(T\)(长度 \(M\)),可以将 \(T\) 中字符循环增加(
9变0),求使 \(T\) 成为 \(S\) 的子串的最少操作次数。这是一个字符串匹配 + 循环距离问题,核心在于枚举 \(S\) 中所有长度为 \(M\) 的子串,计算与 \(T\) 的循环数字距离。 -
算法选择:
- 枚举子串 + 循环距离计算:枚举 \(S\) 中所有长度为 \(M\) 的子串起始位置,对每个位置计算与 \(T\) 的逐位循环距离,取最小值
-
关键步骤:
- 读入数据:\(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\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N \cdot M)\),枚举 \(N-M+1\) 个起始位置,每个比较 \(M\) 位
- 空间复杂度:\(O(N + M)\),存储字符串
-
循环距离与枚举匹配的核心思想:
- 循环数字距离:数字
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
浙公网安备 33010602011771号