题解:学而思编程 疯狂的科学家

【题目来源】

学而思编程:疯狂的科学家

【题目描述】

Farmer John 的远房亲戚 Ben 是一个疯狂的科学家。通常这会在家庭聚会时造成不小的摩擦,但这偶尔也会带来些好处,尤其是当 Farmer John 发现他正面对一些有关他的奶牛们的独特而不寻常的问题时。

Farmer John 当前正面对一个有关她的奶牛们的独特而不寻常的问题。他最近订购了 \(N\) 头奶牛(\(1≤N≤1000\)),包含两种不同品种:荷斯坦牛和更赛牛。他在订单中用一个长为 \(N\) 的字符串来指定奶牛,其中的字符为 H(表示荷斯坦牛)或 G(表示更赛牛)。不幸的是,当这些奶牛到达他的农场,他给她们排队时,她们的品种组成的字符串与原先的不同。

我们将这两个字符串称为 \(A\)\(B\),其中 \(A\) 是 Farmer John 原先想要的品种字符组成的字符串,\(B\) 是他的奶牛们到达时组成的字符串。Farmer John 并没有简单地检查重新排列 \(B\) 中的奶牛是否能得到 \(A\),而是请他的远房亲戚 Ben 利用他的科学才华来解决这一问题。

经过数月的研究,Ben 发明了一台不同寻常的机器:奶牛品种转换机,能够选择任意奶牛组成的子串并反转她们的品种:在这个子串中的所有 H 变为 G,所有 G 变为 H。Farmer John 想要求出将他当前的序列 \(B\) 变为他本来订购时想要的 \(A\) 需要使用这台机器的最小次数。然而,Ben 的疯狂的科学家技能并不会处理开发奇异机器以外的事,所以你需要帮助 Farmer John 解决这个计算难题。

【输入】

输入的第一行包含 \(N\),以下两行包含字符串 \(A\)\(B\)。每个字符串均包含 \(N\) 个字符,字符均为 H 和 G 之一。

【输出】

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

【输入样例】

7
GHHHGHH
HHGGGHH

【输出样例】

2

【核心思想】

  1. 问题分析:给定两个长度为 \(N\) 的字符串 \(A\)\(B\)(仅含 HG),每次操作可选择任意子串并翻转其中所有字符(HGGH)。求将 \(B\) 变为 \(A\) 的最小操作次数。这是一个连续段统计问题,关键在于识别 \(A\)\(B\) 不同的连续段,每段恰好需要一次操作。

  2. 算法选择

    • 差异段识别:扫描字符串,找出 \(A\)\(B\) 字符不同的连续区间
    • 贪心计数:每个不同的连续段恰好需要一次翻转操作,段数即为最小操作次数
  3. 关键步骤

    • 初始化:读取 \(N\) 和字符串 \(A\)\(B\)
    • 统计不同段数
      • cnt = 0:不同段计数
      • flag = 1:标记是否处于"可开始新段"状态
      • 遍历 \(i\)\(0\)\(N-1\)
        • a[i] != b[i]flag == 1
          • cnt++:发现新的一段差异
          • flag = 0:标记已进入当前差异段
        • a[i] == b[i]
          • flag = 1:标记可以开始新的差异段
    • 输出 cnt
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N)\),线性扫描字符串
    • 空间复杂度:\(O(1)\),仅需常数变量
  5. 连续段统计的核心思想

    • 操作覆盖性:一次子串翻转可以覆盖任意连续的差异位置,将一段连续差异全部修正
    • 最优性证明:相邻的差异位置若属于同一段,一次操作即可修复;若分开操作则次数更多。因此每段差异恰好对应一次最优操作
    • 状态机设计flag 作为状态机,区分"在差异段内"和"在相同段内",仅在从相同段进入差异段时计数
    • 无重叠约束:翻转操作的选择相互独立,不存在重叠或包含的复杂约束,贪心策略即为最优
    • 适用于"子串翻转/取反 + 最小操作次数"类问题,连续段识别将问题简化为线性计数

【算法标签】

字符串入门

【代码详解】

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int n;  // 字符串长度
    string a, b;  // 两个字符串
    cin >> n >> a >> b;  // 读入长度和两个字符串

    int cnt = 0, flag = 1;  // cnt: 不同段数量,flag: 标记是否在新的一段中

    for (int i = 0; i < n; i++)  // 遍历每个位置
    {
        // 如果当前位置字符不同,且flag为1(表示新的一段开始)
        if (a[i] != b[i] && flag)
        {
            cnt++;  // 不同段数量加1
            flag = 0;  // 标记进入当前不同段
        }

        // 如果当前位置字符相同
        if (a[i] == b[i])
        {
            flag = 1;  // 标记可以开始新的不同段
        }
    }

    cout << cnt << endl;  // 输出不同段的数量
    return 0;
}

【运行结果】

7
GHHHGHH
HHGGGHH
2
posted @ 2026-07-05 14:22  团爸讲算法  阅读(5)  评论(0)    收藏  举报