题解:洛谷 P2957 Barn Echoes

【题目来源】

洛谷:P2957 [USACO09OCT] Barn Echoes G - 洛谷

【题目描述】

奶牛们非常享受在牛栏中哞叫,因为她们可以听到她们哞声的回音。虽然有时候并不能完全听到完整的回音。Bessie 曾经是一个出色的秘书,所以她精确地纪录了所有的哞叫声及其回声。她很好奇到底两个声音的重复部分有多长。

输入两个字符串(长度为 \(1\)\(80\) 个字母),表示两个哞叫声。你要确定最长的重复部分的长度。两个字符串的重复部分指的是同时是一个字符串的前缀和另一个字符串的后缀的字符串。

我们通过一个例子来理解题目。考虑下面的两个哞声:

moyooyoxyzooo
yzoooqyasdfljkamo

第一个串的最后的部分 yzooo 跟第二个串的第一部分重复。第二个串的最后的部分 mo 跟第一个串的第一部分重复。所以 yzooomo 都是这 \(2\) 个串的重复部分。其中,yzooo 比较长,所以最长的重复部分的长度就是 \(5\)

【输入】

两行字符串,表示一只奶牛的哞叫声及其回声。

【输出】

一行一个整数,表示两个声音的重复部分有多长。

【输入样例】

abcxxxxabcxabcd
abcdxabcxxxxabcx

【输出样例】

11

【核心思想】

  1. 问题分析:给定两个字符串 \(S_1\)\(S_2\),求最长的字符串 \(T\),使得 \(T\) 同时是 \(S_1\) 的前缀和 \(S_2\) 的后缀,或者 \(T\) 同时是 \(S_2\) 的前缀和 \(S_1\) 的后缀。这是一个字符串匹配/暴力枚举问题,关键在于枚举所有可能的前缀-后缀匹配长度并验证。

  2. 算法选择

    • 暴力枚举:枚举所有可能的重叠长度,检查 \(S_1\) 的前缀是否与 \(S_2\) 的后缀匹配,以及 \(S_2\) 的前缀是否与 \(S_1\) 的后缀匹配
    • 双方向检查:分别检查两种方向(\(S_1\) 前缀对 \(S_2\) 后缀,\(S_2\) 前缀对 \(S_1\) 后缀),取最大值
  3. 关键步骤

    • 读取输入:字符串 \(S_1\)\(S_2\)
    • 方向一:\(S_1\) 前缀 vs \(S_2\) 后缀
      • \(S_1\) 最后一个字符 \(c_1 = S_1[len_1-1]\)
      • 从后往前遍历 \(S_2\),找到与 \(c_1\) 相同的位置 \(i\)
      • 检查 \(S_2[0..i]\) 是否与 \(S_1[len_1-1-i..len_1-1]\) 完全匹配
      • 若匹配,更新 \(maxn = \max(maxn, i+1)\)
    • 方向二:\(S_2\) 前缀 vs \(S_1\) 后缀
      • 对称处理,取 \(S_2\) 最后一个字符 \(c_2 = S_2[len_2-1]\)
      • 从后往前遍历 \(S_1\),找到与 \(c_2\) 相同的位置 \(i\)
      • 检查 \(S_1[0..i]\) 是否与 \(S_2[len_2-1-i..len_2-1]\) 完全匹配
      • 若匹配,更新 \(maxn = \max(maxn, i+1)\)
    • 输出 \(maxn\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(L_1 \cdot L_2)\),最坏情况下双重循环比较
    • 空间复杂度:\(O(L_1 + L_2)\),存储两个字符串
  5. 暴力匹配的核心思想

    • 前缀-后缀的对偶性:重复部分必须满足"一个串的前缀 = 另一个串的后缀",通过枚举后缀结束位置来定位可能匹配
    • 末字符锚定:以最后一个字符为锚点,从后往前寻找可能的匹配起点,减少无效枚举
    • 完全匹配验证:找到候选位置后,逐字符验证整个前缀-后缀是否完全一致,避免部分匹配误判
    • 适用于"短字符串前缀-后缀匹配"类问题,核心是通过末字符缩小搜索范围后暴力验证

【算法标签】

普及- #字符串入门

【代码详解】

#include <bits/stdc++.h>
using namespace std;
string s1, s2;
int maxn = -1e9;
int main()
{
    cin >> s1 >> s2;  // 输入s1和s2字符串
    int len1 = s1.length();  // 获取s1长度
    int len2 = s2.length();  // 获取s2长度
    char c1 = s1[len1-1];  // 取得s1字符串的最后一个字符
    for (int i=len2-1; i>=0; i--) {  // 在s2字符串从后往前找到第一个与c1相同的字符
        if (s2[i]==c1) {
            int cnt=0;  // 定义计数器
            for (int j=0; j<=i; j++) {  // 从s2字符串的第0个字符往后找到i处
                if (s2[j]==s1[len1-1-i+j]) {  // 统计两段字符串的匹配程度
                    cnt++;
                }
            }
            if (cnt==i+1) maxn = max(maxn, cnt);  // 如果均匹配,得到长度
        }
    }
    char c2 = s2[len2-1];  // 取得s2字符串的最后一个字符
    for (int i=len1-1; i>=0; i--) {  // 在s1字符串从后往前找到第一个与c2相同的字符
        if (s1[i]==c2) {
            int cnt=0;  // 定义计数器
            for (int j=0; j<=i; j++) {  // 从s2字符串的第0个字符往后找到i处
                if (s1[j]==s2[len2-1-i+j]) {  // 统计两段字符串的匹配程度
                    cnt++;
                }
            }
            if (cnt==i+1) maxn = max(maxn, cnt);  // 如果均匹配,得到长度

        }
    }
    cout << maxn << endl;  // 输出最长的匹配字符串长度
    return 0;
}

【运行结果】

abcxxxxabcxabcd
abcdxabcxxxxabcx
11
posted @ 2026-08-28 15:56  团爸讲算法  阅读(5)  评论(0)    收藏  举报