字符串-回文字符串(Manacher算法)

问题描述

小蓝最近迷上了回文字符串,他有一个只包含小写字母的字符串 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 的开头添加任意数量的指定字符(lqb),将 S 转化为一个回文字符串。

  • 回文字符串:一个字符串如果正读和反读相同,则称为回文字符串。

  • 操作:只能在字符串开头添加指定字符,不能修改或删除原有字符。


2. 关键观察

1. 回文字符串的性质
  • 回文字符串的核心是对称性。

  • 如果字符串 S 本身已经是回文字符串,那么无需任何操作。

  • 如果 S 不是回文字符串,我们需要通过添加字符使其对称。

2. 添加字符的位置
  • 只能在字符串开头添加字符。

  • 这意味着我们需要找到一个最长的前缀回文串,然后检查剩余部分是否可以通过添加指定字符使其对称。

3. Manacher 算法的适用性
  • Manacher 算法可以在 O(n)的时间复杂度内找到字符串中的所有回文子串。

  • 通过 Manacher 算法,我们可以高效地找到以 S 开头的最长回文串。


3. 解决思路

基于以上观察,我们可以设计以下解决思路:

  1. 构建扩展字符串 a

    • 将原字符串 S 转换为扩展字符串 a,以便统一处理奇偶长度的回文串。

    • 扩展规则:奇数位置填充原字符串的字符,偶数位置填充 #

  2. 使用 Manacher 算法计算最长回文半径

    • 使用 Manacher 算法计算每个位置 a[i] 的最长回文半径 m[i]

  3. 找到以 a[1] 开头的最长回文串

    • 遍历扩展字符串 a,找到以 a[1] 开头的最长回文串的右边界 p

  4. 检查剩余部分是否合法

    • 检查 p + 1 到 2 * n - 1 的部分是否只包含 #lqb

    • 如果包含其他字符,则无法通过添加指定字符使 S 成为回文字符串。

  5. 输出结果

    • 如果检查通过,输出 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 的部分是否只包含 #lqb

  • 如果包含其他字符,则无法通过添加指定字符使 S 成为回文字符串。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值