题解:AtCoder AT_abc468_d Pre-Palindrome

【题目来源】

AtCoder:Pre-Palindrome

【题目描述】

A string consisting of lowercase English letters is called a good string if it satisfies the following condition.

  • It can be turned into a palindrome by rewriting at most one character.

For example, a, iwai, and abcdcza are good strings, but abcd and atcoder are not good strings. Note that, in particular, a palindrome is also a good string.

You are given a string \(S\) consisting of lowercase English letters.

Find the number of non-empty substrings (contiguous subsequences) of \(S\) that are good strings.

Two substrings taken from different positions of \(S\) are counted separately even if they are equal as strings.

What is a substring?

A substring of \(S\) is a string obtained by deleting zero or more characters from the beginning and zero or more characters from the end of \(S\).
For example, ab is a substring of abc, but ac is not a substring of abc.

由小写英文字母组成的字符串如果满足以下条件,则称为好字符串

  • 通过重写最多一个字符,它可以变成一个回文串。

例如,aiwaiabcdcza 是好字符串,但 abcdatcoder 不是好字符串。注意,特别地,回文串本身也是好字符串。

给定一个由小写英文字母组成的字符串 \(S\)

\(S\) 中是好字符串的非空子串(连续子序列)的数量。

即使两个子串作为字符串相等,只要它们取自 \(S\) 的不同位置,就分别计数。

什么是子串?

\(S\)子串是指从 \(S\) 的开头删除零个或多个字符、并从末尾删除零个或多个字符后得到的字符串。
例如,ababc 的子串,但 ac 不是 abc 的子串。

【输入】

The input is given from Standard Input in the following format:

\(S\)

【输出】

Output the answer.

【输入样例】

ababa

【输出样例】

13

【核心思想】

  1. 问题分析:给定字符串 \(S\),求其中有多少个非空子串是"好字符串"——即通过修改最多一个字符可以变成回文串。回文串本身也是好字符串。这是一个双指针扩展 + 回文判定问题,核心在于利用回文的对称性,从中心向两边扩展并统计不匹配字符数。

  2. 算法选择

    • 中心扩展法:枚举每个可能的回文中心(奇数长度为中心字符,偶数长度为中心缝隙),向两边扩展并统计不匹配数
    • 提前终止:不匹配数超过 \(1\) 时立即停止扩展
  3. 关键步骤

    • 读入数据:读取字符串 \(S\),长度 \(n\)
    • 奇数长度子串(中心 \(mid\)\(0\)\(n-1\)):
      • \(l = r = mid\)\(mis = 0\)
      • \(ans \leftarrow ans + 1\)(长度为 \(1\) 总是好字符串)
      • 向两边扩展(\(l > 0\)\(r < n-1\)):
        • \(l \leftarrow l - 1\)\(r \leftarrow r + 1\)
        • \(mis \leftarrow mis + [s[l] \neq s[r]]\)
        • \(mis \leq 1\)\(ans \leftarrow ans + 1\)
        • 否则 \(break\)
    • 偶数长度子串(中心缝隙 \(mid\)\(0\)\(n-2\)):
      • \(l = mid\)\(r = mid + 1\)\(mis = [s[l] \neq s[r]]\)
      • \(mis \leq 1\)\(ans \leftarrow ans + 1\),然后向两边扩展(同奇数情况)
    • 输出结果\(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n^2)\),最坏情况每个中心扩展 \(O(n)\),共 \(2n\) 个中心
    • 空间复杂度:\(O(1)\),仅使用指针和计数器
  5. 中心扩展与提前终止的核心思想

    • 回文的对称性:从中心向两边扩展时,新加入的字符对 \((s[l], s[r])\) 若相同则不影响回文性,若不同则增加一次"修改需求"
    • 修改次数的单调性:向两边扩展只会增加或保持不匹配数,不会减少。因此一旦 \(mis > 1\),后续扩展必然不满足条件,可直接终止
    • 两种中心类型:奇数长度子串有 \(n\) 个中心点,偶数长度子串有 \(n-1\) 个中心缝隙,覆盖所有可能的子串
    • 计数策略:每个满足 \(mis \leq 1\) 的扩展状态对应一个好字符串子串,直接累加。由于不同位置的相同子串分别计数,无需去重
    • 适用于回文相关子串统计、中心扩展类字符串问题

【算法标签】

模拟

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long    // 将int定义为long long,避免子串数量过多导致溢出
string s;               // 输入的字符串S
int ans;                // ans记录好字符串子串的总数

signed main()           // 使用signed main配合#define int long long
{
    cin >> s;           // 读入字符串S
    int n = s.size();   // n为字符串S的长度

    // 第一步:枚举奇数长度子串的中心点(中心为单个字符)
    for (int mid = 0; mid < n; mid++)  // 遍历每个可能的中心位置
    {
        int l = mid, r = mid;          // 初始化左右指针都指向中心点,形成长度为1的子串
        int mis = 0;                   // mis记录当前子串中不匹配的对称位置对数(即需要修改的字符数)
        ans++;                         // 长度为1的子串总是回文串(0次修改),一定是好字符串,直接计数
        while (l > 0 && r < n - 1)     // 向两边扩展,确保不越界
        {
            l--;                       // 左指针向左移动
            r++;                       // 右指针向右移动
            mis += (s[l] != s[r]);     // 如果新扩展的两端字符不同,不匹配数加1
            if (mis <= 1)              // 如果不匹配数不超过1(最多修改1个字符可变成回文)
                ans++;                 // 该子串是好字符串,计数加1
            else                       // 如果不匹配数超过1
                break;                 // 继续扩展只会增加不匹配数,直接退出
        }
    }

    // 第二步:枚举偶数长度子串的中心点(中心为两个相邻字符之间)
    for (int mid = 0; mid < n - 1; mid++)  // 遍历每对相邻字符作为中心
    {
        int l = mid, r = mid + 1;      // 初始化左右指针指向相邻的两个字符,形成长度为2的子串
        int mis = (s[l] != s[r]);      // 初始不匹配数为中间两个字符是否相同(0或1)
        if (mis <= 1)                  // 如果长度为2的子串最多需要修改1个字符
        {
            ans++;                     // 该长度为2的子串是好字符串,计数加1
            while (l > 0 && r < n - 1) // 向两边扩展,确保不越界
            {
                l--;                   // 左指针向左移动
                r++;                   // 右指针向右移动
                mis += (s[l] != s[r]); // 如果新扩展的两端字符不同,不匹配数加1
                if (mis <= 1)          // 如果不匹配数不超过1
                    ans++;             // 该子串是好字符串,计数加1
                else                   // 如果不匹配数超过1
                    break;             // 继续扩展只会增加不匹配数,直接退出
            }
        }
    }

    cout << ans << endl;               // 输出好字符串子串的总数

    return 0;
}

【运行结果】

ababa
13
posted @ 2026-07-26 17:32  团爸讲算法  阅读(1)  评论(0)    收藏  举报