题解:洛谷 P3741 小果的键盘
【题目来源】
【题目描述】
一天,她打出了一个只有这两个字符的字符串。当这个字符串里含有 VK 这个字符串的时候,honoka 就特别喜欢这个字符串。所以,她想改变至多一个字符(或者不做任何改变)来最大化这个字符串内 VK 出现的次数。给出原来的字符串,请计算她最多能使这个字符串内出现多少次 VK(只有当 V 和 K 正好相邻时,我们认为出现了 VK。)
【输入】
第一行给出一个数字 \(n\),代表字符串的长度。
第二行给出一个字符串 \(s\)。
【输出】
第一行输出一个整数代表所求答案。
【输入样例】
20
VKKKKKKKKKVVVVVVVVVK
【输出样例】
3
【核心思想】
-
问题分析:给定一个只包含
V和K的字符串,最多可以修改一个字符(V变K或K变V)或不修改,目标是使字符串中相邻的VK子串出现次数最多。本质是一个字符串贪心匹配问题,需要先统计原有VK对,再判断是否可以通过一次修改创造一个新的VK对。 -
算法选择:
- 贪心两遍扫描:
- 第一遍:从左到右统计所有现存的
VK对,并将它们标记为已使用(避免重叠干扰后续修改判断)。这一步尽可能多地保留原有配对。 - 第二遍:在剩余未标记的字符中,检查是否存在
VV或KK的相邻组合。因为修改其中一个字符可以形成一个新的VK,且不会与已有的VK重叠。
- 第一遍:从左到右统计所有现存的
- 正确性:由于一个字符只能属于一个
VK对,第一遍贪心统计所有不重叠的VK对是最优的。之后若存在可修改的位置,最多增加一个额外的VK。
- 贪心两遍扫描:
-
关键步骤:
- 读入:读取
n和字符串s。 - 第一遍统计:遍历
i从0到n-2,若s[i]=='V' && s[i+1]=='K',则ans++,并将这两个字符标记为'A'(或任意非V/K字符),防止它们被第二遍重复使用。 - 第二遍检查:再次遍历
i从0到n-2,若s[i]=='V' && s[i+1]=='V'或s[i]=='K' && s[i+1]=='K',则ans++并跳出(因为最多修改一次)。 - 输出:
ans。
- 读入:读取
-
时间/空间复杂度:
- 时间复杂度:\(O(n)\),只需两次线性扫描。
- 空间复杂度:\(O(1)\),在原字符串上修改,额外空间常数。
-
贪心与字符串修改:
- 避免重叠:标记已使用的
VK对非常重要,否则第二遍可能会基于已经配对的字符进行修改,导致计数错误(例如VVK中,第一遍统计了VK,第二遍若看到VV可能会错误地增加计数)。 - 最多一次修改:第二遍只要找到一个可行的
VV或KK,就足以通过一次修改增加一个VK,因此找到后立即跳出循环。 - 特殊情形:如果字符串本身没有
VK,但存在VV或KK,则可以通过一次修改得到 1 个VK。 - 适用场景:这类“最多修改一次最大化特定子串数量”的贪心问题,常见于字符串处理竞赛题。
- 避免重叠:标记已使用的
【算法标签】
入门 #字符串入门
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n, ans = 0; // n: 字符串长度,ans: 计数器
string s; // 输入的字符串
cin >> n >> s; // 读入字符串长度和字符串
// 第一遍遍历:查找"VK"组合
for (int i = 0; i < n - 1; i++)
{
if (s[i] == 'V' && s[i + 1] == 'K') // 找到"VK"组合
{
ans++; // 计数器加1
s[i] = 'A'; // 将'V'标记为已使用
s[i + 1] = 'A'; // 将'K'标记为已使用
}
}
// 第二遍遍历:查找可以修改一次的额外组合
for (int i = 0; i < n - 1; i++)
{
if (s[i] == 'V' && s[i + 1] == 'V') // 找到"VV"组合
{
ans++; // 可以修改一个字符得到"VK"
break; // 只需要一次修改
}
if (s[i] == 'K' && s[i + 1] == 'K') // 找到"KK"组合
{
ans++; // 可以修改一个字符得到"VK"
break; // 只需要一次修改
}
}
cout << ans; // 输出最大"VK"数量
return 0;
}
【运行结果】
20
VKKKKKKKKKVVVVVVVVVK
3
浙公网安备 33010602011771号