题解:AtCoder AT_awc0098_c Highway Discount Pass
【题目来源】
AtCoder:C - Highway Discount Pass
【题目描述】
Takahashi is about to drive along a long highway. This highway has \(N\) toll gates lined up in a row, numbered from \(1\) to \(N\), and each toll gate \(i\) is assigned an identification code consisting of a single lowercase alphabet letter. When the identification codes of the \(N\) toll gates are arranged in order of their numbers, a string \(S\) of length \(N\) is obtained. That is, the \(i\)-th character of \(S\) is the identification code of toll gate \(i\).
Takahashi must pass through all toll gates from toll gate \(1\) to toll gate \(N\) in ascending order of their numbers. Normally, passing through each toll gate takes a travel time of \(1\), so without any optimization, the total travel time is \(N\).
However, Takahashi has a discount pass. The discount pass is represented by a string \(T\) of length \(M\). By using this discount pass, it may be possible to pass through exactly \(M\) consecutive toll gates together with a travel time of \(1\). The discount pass can be used any number of times, but the intervals to which it is applied must not overlap.
Specifically, the conditions for applying the discount pass are as follows. For an interval consisting of \(M\) consecutive toll gates, namely toll gates \(l, l+1, \ldots, l+M-1\) (\(1 \leq l \leq N-M+1\)), if the string formed by arranging their identification codes in order (the substring of \(S\) from the \(l\)-th character to the \((l+M-1)\)-th character) matches \(T\), then the discount pass can be applied to that interval. When applied, those \(M\) toll gates can be passed together with a travel time of \(1\) (instead of the usual travel time of \(M\)).
Takahashi can choose \(0\) or more intervals to apply the discount pass, but no two chosen intervals may share a common toll gate. It is fine for the last toll gate of one interval and the first toll gate of another interval to have consecutive numbers (i.e., be adjacent).
Toll gates not included in any interval where the discount pass is applied are passed individually, each taking a travel time of \(1\). If the number of intervals where the discount pass is applied is \(k\), the total travel time is \(k + (N - kM) = N - k(M - 1)\).
Find the minimum total travel time for Takahashi to pass through all toll gates.
高橋即将驾车行驶一条长高速公路。这条高速公路上有 \(N\) 个收费站排成一排,编号从 \(1\) 到 \(N\),每个收费站 \(i\) 被分配了一个由单个小写英文字母组成的识别码。当将 \(N\) 个收费站的识别码按编号顺序排列时,得到一个长度为 \(N\) 的字符串 \(S\)。也就是说,\(S\) 的第 \(i\) 个字符是收费站 \(i\) 的识别码。
高橋必须按编号升序从收费站 \(1\) 通过到收费站 \(N\)。正常情况下,通过每个收费站需要 \(1\) 的通行时间,因此没有任何优化时,总通行时间为 \(N\)。
然而,高橋有一张折扣通行证。该折扣通行证由长度为 \(M\) 的字符串 \(T\) 表示。通过使用这张折扣通行证,可能可以将恰好 \(M\) 个连续的收费站一起通过,且通行时间仅为 \(1\)。折扣通行证可以使用任意多次,但应用的区间不能重叠。
具体地,应用折扣通行证的条件如下。对于由 \(M\) 个连续收费站组成的区间,即收费站 \(l, l+1, \ldots, l+M-1\)(\(1 \leq l \leq N-M+1\)),如果按顺序排列它们的识别码所形成的字符串(即 \(S\) 从第 \(l\) 个字符到第 \((l+M-1)\) 个字符的子串)与 \(T\) 匹配,则可以将折扣通行证应用于该区间。应用时,这 \(M\) 个收费站可以一起通过,通行时间为 \(1\)(而不是通常的通行时间 \(M\))。
高橋可以选择 \(0\) 个或多个区间应用折扣通行证,但任意两个选中的区间不能共享同一个收费站。一个区间的最后一个收费站与另一个区间的第一个收费站编号连续(即相邻)是允许的。
未被包含在任何折扣通行证应用区间中的收费站需逐个通过,每个通行时间为 \(1\)。如果应用折扣通行证的区间数量为 \(k\),则总通行时间为 \(k + (N - kM) = N - k(M - 1)\)。
求高橋通过所有收费站的最小总通行时间。
【输入】
\(N\) \(M\)
\(S\)
\(T\)
- The first line contains an integer \(N\) representing the number of toll gates and an integer \(M\) representing the length of the discount pass, separated by a space.
- The second line contains a string \(S\) of length \(N\), which is the identification codes of the toll gates arranged in order of their numbers.
- The third line contains a string \(T\) of length \(M\), representing the discount pass.
【输出】
Output in one line the minimum total travel time for Takahashi to pass through all toll gates.
【输入样例】
8 3
abcxxabc
abc
【输出样例】
4
【核心思想】
-
问题分析:给定字符串 \(S\)(长度 \(N\))和模式串 \(T\)(长度 \(M\)),需要找到所有 \(T\) 在 \(S\) 中的匹配位置。每个匹配区间 \([l, l+M-1]\) 可将通行时间从 \(M\) 降为 \(1\),节省 \(M-1\) 时间。区间不能重叠,目标是最大化选中的不重叠区间数量 \(k\),最小化总通行时间 \(N - k(M-1)\)。关键观察是:先通过 KMP 算法 找到所有匹配位置,再用贪心策略选择最多不重叠区间。
-
算法选择:
- KMP 字符串匹配:\(O(N+M)\) 找到所有 \(T\) 在 \(S\) 中的出现位置
- 贪心选择不重叠区间:按起始位置排序,每次选择起始位置在上一个区间结束之后且结束最早的区间
-
关键步骤:
- KMP 预处理
ne数组:ne[i]表示 \(T[1..i]\) 的最长相等真前缀和真后缀长度- 遍历 \(i\) 从 \(2\) 到 \(M\),利用已计算的
ne值递推
- KMP 匹配:
- 遍历 \(S\) 的每个字符,失配时利用
ne回退,匹配成功时推进 - 当 \(j = M\) 时记录匹配起始位置 \(i - M + 1\),然后 \(j = ne[j]\) 继续寻找重叠匹配
- 遍历 \(S\) 的每个字符,失配时利用
- 贪心选择:
- 初始化
last = -1(上一个选中区间的结束位置) - 遍历所有匹配位置:若当前区间起始 \(pos > last\),则选中该区间,
cnt++,last = pos + M - 1
- 初始化
- 输出答案:\(N - cnt \times (M - 1)\)
- KMP 预处理
-
时间/空间复杂度:
- 时间复杂度:\(O(N + M)\),KMP 线性匹配,贪心线性扫描
- 空间复杂度:\(O(N + M)\),存储字符串、
ne数组和匹配位置
-
KMP + 贪心区间选择的核心思想:
- 前缀函数的最优性:
ne数组记录了模式串自身的结构信息,使得失配时无需回溯主串指针,将暴力匹配的 \(O(NM)\) 优化至 \(O(N+M)\) - 自动机思想:KMP 的匹配过程可视为一个确定性有限自动机,每个状态(已匹配长度)在遇到不同字符时有确定的转移,预处理
ne即构建该自动机 - 贪心正确性:对于区间调度问题,按结束时间最早选择可获得最多不重叠区间。由于所有匹配区间等长(均为 \(M\)),按起始位置排序后顺序选择即等价于按结束时间排序
- 问题转化技巧:将最小化通行时间转化为最大化节省的时间,再转化为最大化不重叠区间数,层层简化使算法设计清晰
- 适用于字符串模式匹配、区间调度最大化、资源分配优化等场景
- 前缀函数的最优性:
【算法标签】
KMP
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 1000005; // 最大字符串长度
int n, m, cnt; // n: S串长度, m: T串长度, cnt: 选中的匹配区间数量
char s[N], p[N]; // s: 收费站识别码字符串(下标从1开始), p: 折扣通行证字符串
int ne[N]; // KMP的next数组(前缀函数),ne[i]表示p[1..i]的最长相等前后缀长度
vector<int> matches; // 存储所有T在S中匹配的起始位置
int main()
{
cin >> n >> m; // 读入N和M
// 读入S串(下标从1开始)
for (int i = 1; i <= n; i++)
cin >> s[i];
// 读入T串(下标从1开始)
for (int i = 1; i <= m; i++)
cin >> p[i];
// ========== KMP预处理:求T串的next数组 ==========
// ne[i] = p[1..i] 的最长相等真前缀和真后缀的长度
for (int i = 2, j = 0; i <= m; i++)
{
while (j && p[i] != p[j + 1]) // 不匹配时,回退到next[j]
j = ne[j];
if (p[i] == p[j + 1]) // 匹配成功,j++
j++;
ne[i] = j; // 记录p[1..i]的最长相等前后缀长度
}
// ========== KMP匹配:在S中查找所有T的出现位置 ==========
for (int i = 1, j = 0; i <= n; i++)
{
while (j && s[i] != p[j + 1]) // 不匹配时,回退到next[j]
j = ne[j];
if (s[i] == p[j + 1]) // 匹配成功,j++
j++;
if (j == m) // 完整匹配了一个T
{
// 记录匹配起始位置:当前位置i减去T的长度再加1
matches.push_back(i - m + 1);
j = ne[j]; // 继续匹配,回退到next[j](允许重叠匹配)
}
}
// ========== 贪心选择不重叠的匹配区间(最大化区间数量) ==========
// 策略:按起始位置排序,每次选择最早结束且不与上一个重叠的区间
int last = -1; // 上一个选中区间的结束位置
for (int pos : matches)
{
int end = pos + m - 1; // 当前匹配区间的结束位置
if (pos > last) // 当前区间起始位置在上一个区间结束之后(不重叠)
{
cnt++; // 选中该区间
last = end; // 更新last为当前区间的结束位置
}
}
// 输出最小总通行时间
// 总时间 = N - cnt * (M - 1)
// 解释:每个选中的区间节省 (M-1) 的时间(M个收费站一起通过只需1,而不是M)
cout << n - cnt * (m - 1) << endl;
return 0;
}
【运行结果】
8 3
abcxxabc
abc
4
浙公网安备 33010602011771号