题解: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.
由小写英文字母组成的字符串如果满足以下条件,则称为好字符串。
- 通过重写最多一个字符,它可以变成一个回文串。
例如,a、iwai 和 abcdcza 是好字符串,但 abcd 和 atcoder 不是好字符串。注意,特别地,回文串本身也是好字符串。
给定一个由小写英文字母组成的字符串 \(S\)。
求 \(S\) 中是好字符串的非空子串(连续子序列)的数量。
即使两个子串作为字符串相等,只要它们取自 \(S\) 的不同位置,就分别计数。
什么是子串?
\(S\) 的子串是指从 \(S\) 的开头删除零个或多个字符、并从末尾删除零个或多个字符后得到的字符串。
例如,ab 是 abc 的子串,但 ac 不是 abc 的子串。
【输入】
The input is given from Standard Input in the following format:
\(S\)
【输出】
Output the answer.
【输入样例】
ababa
【输出样例】
13
【核心思想】
-
问题分析:给定字符串 \(S\),求其中有多少个非空子串是"好字符串"——即通过修改最多一个字符可以变成回文串。回文串本身也是好字符串。这是一个双指针扩展 + 回文判定问题,核心在于利用回文的对称性,从中心向两边扩展并统计不匹配字符数。
-
算法选择:
- 中心扩展法:枚举每个可能的回文中心(奇数长度为中心字符,偶数长度为中心缝隙),向两边扩展并统计不匹配数
- 提前终止:不匹配数超过 \(1\) 时立即停止扩展
-
关键步骤:
- 读入数据:读取字符串 \(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\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n^2)\),最坏情况每个中心扩展 \(O(n)\),共 \(2n\) 个中心
- 空间复杂度:\(O(1)\),仅使用指针和计数器
-
中心扩展与提前终止的核心思想:
- 回文的对称性:从中心向两边扩展时,新加入的字符对 \((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
浙公网安备 33010602011771号