题解:洛谷 P9012 Moo Operations

【题目来源】

洛谷:P9012 [USACO23JAN] Moo Operations B - 洛谷

【题目描述】

农夫约翰给了奶牛贝西 \(Q\) 个新字符串 \((1\le Q\le 100)\) ,其中只有字符 MO ,她想将 \(Q\) 个字符串都变成 MOO

贝西可以用如下的方式改变字符串:

  • 用相反的字符替换第一个或最后一个字符(将 M 变成 O ,将 O 变成 M )。
  • 删除第一个或最后一个字符。

贝西只想用最少的次数完成改变。请你帮她找到需要的最小改变次数。如果不可能在有限的步数中完成这个任务,请输出 -1

【输入】

输入数据的第一行是一个正整数 \(Q\)

接下来的 \(Q\) 行中,每行一个只包含大写字母 MO 的字符串 \(S\) ,保证 \(1\le |S|\le 100\)

【输出】

输出 \(Q\) 行,每行为该测试点最小操作次数,如果不可能在有限的步数中完成这个任务,请输出 -1

【输入样例】

3
MOMMOM
MMO
MOO

【输出样例】

4
-1
0

【核心思想】

  1. 问题分析:给定一个只含 MO 的字符串,每次可翻转或删除两端的字符,问最少多少次操作能将其变为目标串 "MOO"。本质是在双端操作限制下,寻找保留一个长度为3的子串作为最终 "MOO" 的“内核”,其余字符全部删掉,并按需翻转该子串中的字符。由于翻转操作只作用于当前字符串的两端,能保留下来作为最终 "MOO" 的三个字符,在原串中必须是连续的(因为只能删除两端,无法打乱顺序)。

  2. 算法选择

    • 字符串匹配 + 分类讨论:问题的关键并非搜索,而是观察目标串 "MOO" 的结构与操作特性。任何操作都发生在两端,因此最终保留下来的三个字符在原串中一定形成一个连续子串。只需在原串中寻找长度3的连续子串,看它最少通过多少次翻转能变成 "MOO",删除次数固定为 \(L-3\)
    • 四种可行模式判定:只有4种子串模式能通过翻转变成 "MOO""MOO"(0次翻转)、"MOM"(翻转最后1个)、"OOO"(翻转第1个)、"OOM"(翻转第1个和最后1个,共2次),其他模式因中间字符无法通过两端翻转改变而不可行。
  3. 关键步骤

    • 读入 \(Q\),对每个字符串 \(S\),令 \(L = |S|\)
    • \(L < 3\),无法保留三个字符,直接输出 -1
    • find 查找是否存在以下子串,按优先级匹配并计算操作次数:
      • 子串为 "MOO":操作次数 = \(L - 3\)(仅需删除其余字符)
      • 子串为 "MOM":操作次数 = \(L - 3 + 1\)(删除 + 将最后一个 M 翻转为 O
      • 子串为 "OOO":操作次数 = \(L - 3 + 1\)(删除 + 将第一个 O 翻转为 M
      • 子串为 "OOM":操作次数 = \(L - 3 + 2\)(删除 + 将第一个 O 翻转为 M,最后一个 M 翻转为 O
    • 若以上四种均不存在,输出 -1
  4. 时间/空间复杂度

    • 时间复杂度:\(O(Q \cdot L)\),对每个字符串用 find 查找4种子串,\(L \le 100\)
    • 空间复杂度:\(O(1)\),仅存储当前字符串
  5. 字符串操作 + 分类讨论的核心思想

    • 双端操作的性质:删除和翻转都只影响当前串的两端,因此最终保留下来的字符在原串中必须是连续的,且相对顺序不变
    • 问题转化:从“如何操作整个字符串”转化为“寻找一个长度为3的连续子串作为最终 "MOO" 的来源”,其余字符全部删除,所需翻转次数仅取决于该子串与目标的差异
    • 操作次数计算:删除操作次数固定为 \(L-3\)(删掉子串外的所有字符);翻转操作次数等于子串中需要改变的位置数,且仅当子串模式属于四种可行情况时才能完成
    • 适用场景:对于只允许在序列两端进行修改和删除的字符串变换问题,优先考虑最终保留下来的连续子串结构,能极大简化问题

【解题思路】

【算法标签】

普及- #字符串入门

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int q, ans;
string s;
int main()
{
    cin >> q;  // 输入q
    while (q--) {  // 遍历q次询问
        cin >> s;
        int len = s.length();  // 定义长度变量(简化后面的代码)
        if (s.find("MOO")>=0 && s.find("MOO")<=len) {  // 如果可以找到MOO
            ans = len - 3;  // 字符串截掉这3位后的数就是要调整的数
            cout << ans << endl;  // 打印结果
            continue;  // 继续下次询问
        }
        if (s.find("MOM")>=0 && s.find("MOM")<=len) {  // 如果可以找到MOM
            ans = len - 3 + 1;  // 除截掉这3位的次数外,还要再加一次M改为O
            cout << ans << endl;  // 打印结果
            continue;  // 继续下次询问
        }
        if (s.find("OOO")>=0 && s.find("OOO")<=len) {  // 如果可以找到OOO
            ans = len - 3 + 1;  // 除截掉这3位的次数外,还要再加一次O改为M
            cout << ans << endl;  // 打印结果
            continue;  // 继续下次询问
        }
        if (s.find("OOM")>=0 && s.find("OOM")<=len) {  // 如果可以找到OOM
            ans = len - 3 + 2;  // 除截掉这3位的次数外,还要再加2次,O改为M,M改为O
            cout << ans << endl;  // 打印结果
            continue;  // 继续下次询问
        }
        cout << -1 << endl;  // 不符合上述场景的则输出-1,说明找不到
    }
    return 0;
}

【运行结果】

3
MOMMOM
4
MMO
-1
MOO
0
posted @ 2026-08-29 09:41  团爸讲算法  阅读(5)  评论(0)    收藏  举报