题解:洛谷 P9950 Mad Scientist
【题目来源】
洛谷:P9950 [USACO20FEB] Mad Scientist B - 洛谷
【题目描述】
Farmer John 的远房亲戚 Ben 是一个疯狂的科学家。通常这会在家庭聚会时造成不小的摩擦,但这偶尔也会带来些好处,尤其是当 Farmer John 发现他正面对一些有关他的奶牛们的独特而不寻常的问题时。
Farmer John 当前正面对一个有关她的奶牛们的独特而不寻常的问题。他最近订购了 \(N\) 头奶牛(\(1\le N\le 1000\)),包含两种不同品种:荷斯坦牛和更赛牛。他在订单中用一个长为 \(N\) 的字符串来指定奶牛,其中的字符为 H(表示荷斯坦牛)或 G(表示更赛牛)。不幸的是,当这些奶牛到达他的农场,他给她们排队时,她们的品种组成的字符串与原先的不同。
我们将这两个字符串称为 \(A\) 和 \(B\),其中 \(A\) 是 Farmer John 原先想要的品种字符组成的字符串,\(B\) 是他的奶牛们到达时组成的字符串。Farmer John 并没有简单地检查重新排列 \(B\) 中的奶牛是否能得到 \(A\),而是请他的远房亲戚 Ben 利用他的科学才华来解决这一问题。
经过数月的研究,Ben 发明了一台不同寻常的机器:奶牛品种转换机 3000,能够选择任意奶牛组成的子串并反转她们的品种:在这个子串中的所有 H 变为 G,所有 G 变为 H。Farmer John 想要求出将他当前的序列 \(B\) 变为他本来订购时想要的 \(A\) 需要使用这台机器的最小次数。然而,Ben 的疯狂的科学家技能并不会处理开发奇异机器以外的事,所以你需要帮助 Farmer John 解决这个计算难题。
【输入】
输入的第一行包含 \(N\),以下两行包含字符串 \(A\) 和 \(B\)。每个字符串均包含 \(N\) 个字符,字符均为 H 和 G 之一。
【输出】
输出将 \(B\) 变为 \(A\) 需要使用机器的最小次数。
【输入样例】
7
GHHHGHH
HHGGGHH
【输出样例】
2
【核心思想】
-
问题分析:给定两个等长字符串 \(A\)(目标)和 \(B\)(当前),每次操作可以选择 \(B\) 的任意一个连续子串,将其中的所有字符翻转(
H↔G)。求将 \(B\) 变成 \(A\) 所需的最少操作次数。每次操作可以反转任意区间,因此问题等价于:找到 \(B\) 中与 \(A\) 不同的位置,用最少的区间覆盖这些位置(每个区间翻转一次,可覆盖连续的一段不同位置)。最少区间数即为不同位置的连续段数。 -
算法选择:
- 贪心扫描:从左到右遍历字符串,每当遇到一个与 \(A\) 不同的位置,就开启一个新的翻转区间,并一直延伸到连续不同的末尾。每遇到一个“不同→相同”的边界,区间数加 1。这种策略能得到最小次数,因为每个连续的“不同段”必须至少翻转一次,而一次翻转恰好可以覆盖整个连续段。
-
关键步骤:
- 读入:输入 \(N\),以及字符串 \(A\) 和 \(B\)(可使用 0-based 索引)。
- 初始化:
ans = 0。 - 遍历位置 \(i\) 从 \(0\) 到 \(N-1\):
- 若 \(A[i] == B[i]\),继续。
- 若 \(A[i] != B[i]\),则找到一个不匹配段的起点,
ans++;然后跳过整个连续不匹配段(内层循环while (i < N && A[i] != B[i]) i++;注意循环结束后,i指向匹配位置或末尾,外层循环的下一次迭代会从该位置继续,但需要小心索引更新,代码中使用i = j - 1的方式实现)。
- 输出
ans。
-
时间/空间复杂度:
- 时间复杂度:\(O(N)\),每个位置被访问常数次。
- 空间复杂度:\(O(N)\) 或 \(O(1)\)(若直接读入字符串)。
-
贪心 + 连续段统计的核心思想:
- 操作下界:每个连续的“不同段”必须至少被翻转一次,因为翻转操作只能处理连续区间,而不同段之间被相同位置隔开,无法在一次操作中同时覆盖两个分离的不同段(否则会误翻中间的相同位置)。
- 可达性:对每个连续不同段执行一次翻转操作,即可将它们全部修正,且不会影响其他段。因此下界可达,最小次数就是不同段的个数。
- 适用场景:适用于“区间反转/翻转”类问题,当操作代价与区间长度无关且只关心是否覆盖时,答案即为需修改位置的连续段数。
【解题思路】

【算法标签】
入门 #双指针
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n; // 字符串长度
int ans = 0; // 计数器,记录需要翻转的连续段数量
char a[1005]; // 存储第一个字符串(当前状态)
char b[1005]; // 存储第二个字符串(目标状态)
// 文件输入输出流(注释掉的代码)
// ifstream filein("breedflip.in");
// ofstream fileout("breedflip.out");
int main()
{
// 输入字符串长度
// filein >> n;
cin >> n;
// 输入第一个字符串
for (int i = 1; i <= n; i++)
{
// filein >> a[i];
cin >> a[i];
}
// 输入第二个字符串(目标状态)
for (int i = 1; i <= n; i++)
{
// filein >> b[i];
cin >> b[i];
}
// 遍历字符串,统计需要翻转的连续段数量
for (int i = 1; i <= n; i++)
{
// 如果当前位置字符匹配,跳过
if (a[i] == b[i])
{
continue;
}
// 找到当前不匹配段的结束位置
for (int j = i; j <= n; j++)
{
// 如果当前位置仍然不匹配,继续向后查找
if (a[j] != b[j])
{
continue;
}
else
{
// 找到匹配的位置,说明不匹配段结束
ans++; // 增加翻转段计数
i = j - 1; // 更新外层循环索引,跳过已处理段
break; // 退出内层循环
}
}
}
// 输出结果
// fileout << ans << endl;
cout << ans << endl;
return 0;
}
【运行结果】
7
GHHHGHH
HHGGGHH
2
浙公网安备 33010602011771号