问题描述
小蓝最近迷上了回文字符串,他有一个只包含小写字母的字符串 S,小蓝可以往字符串 S 的开头处加入任意数目个指定字符: l、q、b(ASCII码分别为: 108、113、98)。小蓝想要知道他是否能通过这种方式把字符串 S 转化为一个回文字符串。
输入格式
输入的第一行包含一个整数 T,表示每次输入包含 T 组数据。
接下来依次描述 T 组数据。
每组数据一行包含一个字符串 S 。
输出格式
输出 T 行,每行包含一个字符串,依次表示每组数据的答案。如果可以将 S 转化为一个回文字符串输出 Yes
,否则输出 No
。
样例输入
3
gmgqlq
pdlbll
aaa
样例输出
Yes
No
Yes
样例说明
对于 gmgqlq,可以在前面加上 qlq→qlqgmgqlq 转化为一个回文字符串;
对于 pdlbll,无法转化为一个回文字符串;
对于 aaa,本身就是一个回文字符串。
解题代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 1e6 + 10;
char a[N << 1], s[N];
int n, m[N << 1];
int main() {
int T; scanf("%d", &T); // 输入测试用例的数量 T
while (T--) {
scanf("%s", s + 1); // 输入字符串 s,索引从 1 开始
n = strlen(s + 1); // 计算字符串 s 的长度 n
// 构建扩展字符串 a,长度为 2 * n - 1
// 奇数位置填充原字符串 s 的字符,偶数位置填充 '#'
for (int i = 1; i <= 2 * n - 1; i++)
a[i] = (i & 1) ? s[(i + 1) / 2] : '#', m[i] = 0;
// 使用 Manacher 算法计算每个位置 a[i] 的最长回文半径 m[i]
for (int l = 1, r = 0, mid = 0; l <= 2 * n - 1; l++) {
if (l <= r) m[l] = min(m[mid * 2 - l], r - l + 1); // 利用对称性快速初始化 m[l]
else m[l] = 1; // 否则初始化为 1
while ((l - m[l]) >= 1 && (l + m[l]) <= (2 * n - 1) && a[l - m[l]] == a[l + m[l]]) m[l]++; // 扩展 m[l]
if ((l + m[l] - 1) > r) mid = l, r = l + m[l] - 1; // 更新最右边界 r 和中心 mid
}
// 找到以 a[1] 开头的最长回文串的右边界 p
int p = 1;
for (int i = 1; i <= 2 * n - 1; i++)
if ((i - m[i] + 1) == 1) p = i + m[i] - 1;
/*
解释:
1. i - m[i] + 1 表示以 a[i] 为中心的最长回文串的左边界。
2. 如果左边界等于 1,说明这个回文串以 a[1] 开头。
3. p = i + m[i] - 1 表示以 a[i] 为中心的最长回文串的右边界。
4. 遍历所有位置 i,找到以 a[1] 开头的最长回文串的右边界 p。
*/
// 检查剩余部分是否只包含指定字符('#'、'l'、'q'、'b')
bool check = 1;
for (int i = p + 1; i <= 2 * n - 1; i++)
if (a[i] != '#' && a[i] != 'l' && a[i] != 'q' && a[i] != 'b') check = 0;
/*
解释:
1. 从 p + 1 到 2 * n - 1 的部分是未匹配的部分。
2. 如果这部分包含除 '#'、'l'、'q'、'b' 之外的字符,则无法通过添加指定字符使 s 成为回文字符串。
*/
// 输出结果
if (check) puts("Yes"); // 如果检查通过,输出 Yes
else puts("No"); // 否则输出 No
}
return 0;
}
1. 问题分析
-
目标:判断是否可以通过在字符串
S
的开头添加任意数量的指定字符(l
、q
、b
),将S
转化为一个回文字符串。 -
回文字符串:一个字符串如果正读和反读相同,则称为回文字符串。
-
操作:只能在字符串开头添加指定字符,不能修改或删除原有字符。
2. 关键观察
1. 回文字符串的性质
-
回文字符串的核心是对称性。
-
如果字符串
S
本身已经是回文字符串,那么无需任何操作。 -
如果
S
不是回文字符串,我们需要通过添加字符使其对称。
2. 添加字符的位置
-
只能在字符串开头添加字符。
-
这意味着我们需要找到一个最长的前缀回文串,然后检查剩余部分是否可以通过添加指定字符使其对称。
3. Manacher 算法的适用性
-
Manacher 算法可以在 O(n)的时间复杂度内找到字符串中的所有回文子串。
-
通过 Manacher 算法,我们可以高效地找到以
S
开头的最长回文串。
3. 解决思路
基于以上观察,我们可以设计以下解决思路:
-
构建扩展字符串
a
:-
将原字符串
S
转换为扩展字符串a
,以便统一处理奇偶长度的回文串。 -
扩展规则:奇数位置填充原字符串的字符,偶数位置填充
#
。
-
-
使用 Manacher 算法计算最长回文半径:
-
使用 Manacher 算法计算每个位置
a[i]
的最长回文半径m[i]
。
-
-
找到以
a[1]
开头的最长回文串:-
遍历扩展字符串
a
,找到以a[1]
开头的最长回文串的右边界p
。
-
-
检查剩余部分是否合法:
-
检查
p + 1
到2 * n - 1
的部分是否只包含#
、l
、q
、b
。 -
如果包含其他字符,则无法通过添加指定字符使
S
成为回文字符串。
-
-
输出结果:
-
如果检查通过,输出
Yes
;否则输出No
。
-
4. 关键点解释
1. Manacher 算法
-
Manacher 算法用于在 O(n)的时间复杂度内找到字符串中的所有回文子串。
-
通过构建扩展字符串
a
,将奇偶长度的回文串统一处理。
2. 扩展字符串 a
-
扩展字符串
a
的长度为2 * n - 1
,奇数位置填充原字符串s
的字符,偶数位置填充#
。 -
这样可以将奇偶长度的回文串统一处理。
3. 最长前缀回文串
-
找到以
a[1]
开头的最长回文串的右边界p
。 -
这个回文串可以通过在开头添加指定字符得到。
4. 剩余部分的合法性检查
-
检查
p + 1
到2 * n - 1
的部分是否只包含#
、l
、q
、b
。 -
如果包含其他字符,则无法通过添加指定字符使
S
成为回文字符串。