题解:洛谷 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\) 个字符,字符均为 HG 之一。

【输出】

输出将 \(B\) 变为 \(A\) 需要使用机器的最小次数。

【输入样例】

7
GHHHGHH
HHGGGHH

【输出样例】

2

【核心思想】

  1. 问题分析:给定两个等长字符串 \(A\)(目标)和 \(B\)(当前),每次操作可以选择 \(B\) 的任意一个连续子串,将其中的所有字符翻转(HG)。求将 \(B\) 变成 \(A\) 所需的最少操作次数。每次操作可以反转任意区间,因此问题等价于:找到 \(B\) 中与 \(A\) 不同的位置,用最少的区间覆盖这些位置(每个区间翻转一次,可覆盖连续的一段不同位置)。最少区间数即为不同位置的连续段数

  2. 算法选择

    • 贪心扫描:从左到右遍历字符串,每当遇到一个与 \(A\) 不同的位置,就开启一个新的翻转区间,并一直延伸到连续不同的末尾。每遇到一个“不同→相同”的边界,区间数加 1。这种策略能得到最小次数,因为每个连续的“不同段”必须至少翻转一次,而一次翻转恰好可以覆盖整个连续段。
  3. 关键步骤

    • 读入:输入 \(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
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N)\),每个位置被访问常数次。
    • 空间复杂度:\(O(N)\)\(O(1)\)(若直接读入字符串)。
  5. 贪心 + 连续段统计的核心思想

    • 操作下界:每个连续的“不同段”必须至少被翻转一次,因为翻转操作只能处理连续区间,而不同段之间被相同位置隔开,无法在一次操作中同时覆盖两个分离的不同段(否则会误翻中间的相同位置)。
    • 可达性:对每个连续不同段执行一次翻转操作,即可将它们全部修正,且不会影响其他段。因此下界可达,最小次数就是不同段的个数。
    • 适用场景:适用于“区间反转/翻转”类问题,当操作代价与区间长度无关且只关心是否覆盖时,答案即为需修改位置的连续段数。

【解题思路】

【算法标签】

入门 #双指针

【代码详解】

#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
posted @ 2026-08-30 09:19  团爸讲算法  阅读(7)  评论(0)    收藏  举报