前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >字符串-Manacher算法(你知道马拉车算法吗?)

字符串-Manacher算法(你知道马拉车算法吗?)

作者头像
唔仄lo咚锵
发布2021-12-31 09:32:37
9860
发布2021-12-31 09:32:37
举报
文章被收录于专栏:blog(为什么会重名,真的醉了)

文章目录

原理

马拉车算法当然不是马拉着车的奇奇怪怪的东西,是Manacher’s Algorithm的音译。 Manacher’s Algorithm马拉车算法是一种可以在O(n) 线性时间内求最长回文子串的算法(Manacher是人名,发明者)。所谓的回文也就是正读反读都是一样的,比如noonlevel ,那在一个字符串中找最长的回文子串,一般使用中心扩展法,也就是枚举每一个字符为对称中心,然后向左右延伸并判断是否相等,但这种方法的时间复杂度是O(n^2)

分析中心扩展法效率差的原因,是因为它做了大量重复计算,比如字符串aaaaaaaaaabababa ,也就是最坏的情况——各个回文子串相互重叠。而马拉车算法则通过一些空间记录和回文对称的特点来避免这种重复计算。

奇偶问题

首先需要解决字符串长度奇偶时,对称中心不一致的问题。如串abaabba 都是回文串,但是它们的对称中心分别是不一致的,前者是b ,后者是位于b 中间。

我们在每个字符前后加上一个’#‘符,并且为了后续不越界和求原下标方便,我们让新字符串t 的下标从1开始,即下标0处也赋’#’。这样处理后字符串的长度就都是奇数了,对称中心自然也都是二分之一处的字符,不会再是位于两字符中间的情况。

在这里插入图片描述
在这里插入图片描述

p[]数组

现在我们引用一个数组p[]来表示中心扩展的最大长度,而它就是去掉’#'后原回文子串的长度,也就是我们求的答案。

以字符串ababaab 为例,通过P 的下标i 减去P[i] 再除以2 就能求出原回文子串,如下标8P[8]=3(8-3)/2=2 ,即在原串S 中下标2 开始长度为3的子串是一个回文子串(aba )。

在这里插入图片描述
在这里插入图片描述

马拉车求p[]

前面都是准备工作,下面才是马拉车的核心算法,它充分利用了回文的对称性,比如说对于回文串ababa ,不难发现它的p[] 数组也是关于中心对称的,如下图:

在这里插入图片描述
在这里插入图片描述

但这只限于回文串中可以这样直接镜像,还需要推导至一般情况,仍以串ababaab 为例。 设mid

为对称中心,r 表示回文串的右边半径(r=mid+p[i] ),下标i 即当前要求的p[i] ,下标j 表示i 关于中心mid 镜像对称的下标,分如下三种情况:

  1. 合法范围内 就是上面讨论的情形,直接赋值即可。
在这里插入图片描述
在这里插入图片描述
  1. j 遇到原字符串左边界 此时不能直接赋值,因为j 位置处是因为向左到尽头越界,而镜像处的j 向左没有越界,应该继续比较,即用中心扩展法。 虽然此处答案都是1,但j 是因为到了首字符‘#’必不相等,而i 是可以匹配的,只是此处ab 匹配失败。
在这里插入图片描述
在这里插入图片描述
  1. i >=r 同上面一样不能利用对称性了,将p[i] 置0,用中心扩展法。

同时注意更新midr ,也就是当更新p[i] 后,求出新的r(p[i]+i) ,若大于旧的r ,就更新midr,即设置当前点i 为新的对称中心。

插播反爬信息 )博主CSDN地址:https://wzlodq.blog.csdn.net/

模板

代码语言:javascript
复制
#include <iostream>
#include <string>
using namespace std;
const int maxn = 1000006;
int p[maxn];
string s, t;
int main() {
    cin >> s;
    int n = s.size();
    t = "#";
    for (int i = 0; i < n; i++) {
        t += '#';
        t += s[i];
    }
    t += '#';
    int m = t.size();
    int mid = 0, r = 0;
    int len = 0, st = 0;//回文串长度和起始位置
    for (int i = 1; i < m - 1; i++) {
        int j = mid * 2 - i;
        if (i < r) //右边半径内
            p[i] = min(r - i, p[j]);//防止超出r
        else p[i] = 0; //>=r情况
        while (t[i + 1 + p[i]] == t[i - 1 - p[i]])
            p[i]++; //中心扩展
        if (i + p[i] > r) { //更新r
            mid = i;
            r = i + p[i];
        }
        if (p[i] > len) { //顺便更新答案
            len = p[i];
            st = (i - p[i]) / 2;
        }
    }
    cout << len << "\n" << s.substr(st, len);
    return 0;
}

注意时间复杂度是O(n) ,当里面的while循环中心扩展后,下次循环是不会再访问到这些节点的,会直接用对称性。

例题

P3805 【模板】manacher算法

洛谷P3805 【模板】manacher算法

题目描述 给出一个只由小写英文字符 \texttt a,\texttt b,\texttt c,\ldots\texttt y,\texttt za,b,c,…y,z 组成的字符串 SS ,求 SS 中最长回文串的长度 。 字符串长度为 nn。 输入格式 一行小写英文字符 \texttt a,\texttt b,\texttt c,\ldots\texttt y,\texttt za,b,c,…y,z 组成的字符串 SS。 输出格式 一个整数表示答案。 输入输出样例 输入 #1 aaa 输出 #1 3 说明/提示 1≤n≤1.1×107

代码语言:javascript
复制
#include <iostream>
#include <string>
using namespace std;
const int maxn=32000007;
int p[maxn]={0};
string s,t;
int main(){
    cin>>s;
    int n=s.size();
    t="#";
    for(int i=0;i<n;i++){
        t+='#';
        t+=s[i];
    }
    t+='#';
    int m=t.size();
    int mid=0,r=0,ans=0;
    for(int i=1;i<m-1;i++){
        int j=mid*2-i;
        if(i<r)
            p[i]=min(r-i,p[j]);
        while(t[i+1+p[i]]==t[i-1-p[i]])
            p[i]++;
        if(i+p[i]>r){
            mid=i;
            r=i+p[i];
        }
        if(p[i]>ans)ans=p[i];
    }
    cout<<ans;
    return 0;
}

P1659 拉拉队排练

洛谷P1659 [国家集训队]拉拉队排练

题目描述 艾利斯顿商学院篮球队要参加一年一度的市篮球比赛了。拉拉队是篮球比赛的一个看点,好的拉拉队往往能帮助球队增加士气,赢得最终的比赛。所以作为拉拉队队长的楚雨荨同学知道,帮助篮球队训练好拉拉队有多么的重要。 拉拉队的选拔工作已经结束,在雨荨和校长的挑选下,n位集优秀的身材、舞技于一体的美女从众多报名的女生中脱颖而出。这些女生将随着篮球队的小伙子们一起,和对手抗衡,为艾利斯顿篮球队加油助威。 一个阳光明媚的早晨,雨荨带领拉拉队的队员们开始了排练。n个女生从左到右排成一行,每个人手中都举了一个写有26个小写字母中的某一个的牌子,在比赛的时候挥舞,为小伙子们呐喊、加油。 雨荨发现,如果连续的一段女生,有奇数个,并且他们手中的牌子所写的字母,从左到右和从右到左读起来一样,那么这一段女生就被称作和谐小群体。 现在雨荨想找出所有和谐小群体,并且按照女生的个数降序排序之后,前K个和谐小群体的女生个数的乘积是多少。由于答案可能很大,雨荨只要你告诉她,答案除以19930726的余数是多少就行了。 输入格式 输入为标准输入。 第一行为两个正整数n和K,代表的东西在题目描述中已经叙述。 接下来一行为n个字符,代表从左到右女生拿的牌子上写的字母。 输出格式 输出为标准输出。 输出一个整数,代表题目描述中所写的乘积除以19930726的余数,如果总的和谐小群体个数小于K,输出一个整数-1。 输入输出样例 输入 #1 5 3 ababa 输出 #1 45 说明/提示 样例说明 和谐小群体女生所拿牌子上写的字母从左到右按照女生个数降序排序后为ababa, aba, aba, bab, a, a, a, b, b,前三个长度的乘积为 。

跑一遍马拉车,并使用cnt[]数组记录回文串长度对应数量,过滤奇数的,从大往小循环即可,另外注意直接乘会超时,用快速幂。

代码语言:javascript
复制
#include <iostream>
#include <string>
using namespace std;
const int maxn = 2000006;
const int mod = 19930726;
typedef long long ll;
int p[maxn], cnt[maxn];
string s, t;
ll n, k;
ll qpow(ll x, ll y) {
    ll res = 1;
    while (y) {
        if (y & 1)res = (res * x) % mod;
        x = (x * x) % mod;
        y >>= 1;
    }
    return res;
}
int main() {
    cin >> n >> k >> s;
    t = "#";
    for (int i = 0; i < n; i++) {
        t += '#';
        t += s[i];
    }
    t += '#';
    ll m = t.size();
    int mid = 0, r = 0;
    for (int i = 1; i < m - 1; i++) {
        int j = mid * 2 - i;
        if (i < r)
            p[i] = min(r - i, p[j]);
        else p[i] = 0; 
        while (t[i + 1 + p[i]] == t[i - 1 - p[i]])
            p[i]++; 
        if (i + p[i] > r) { 
            mid = i;
            r = i + p[i];
        }
        if (p[i] % 2)cnt[p[i]]++;
    }
    ll sum = 0, ans = 1;
    for (int i = n; i >= 1; i--) {
        if (i % 2 == 0)continue;
        sum += cnt[i];
        ll cur = min(sum, k);
        k -= sum;
        ans = (ans * qpow(i, cur)) % mod;
        if (k < 0)break;
    }
    if (k > 0)ans = -1;
    cout << ans;
    return 0;
}
 
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/05/17 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 原理
    • 奇偶问题
      • p[]数组
        • 马拉车求p[]
          • 模板
          • 例题
            • P3805 【模板】manacher算法
              • P1659 拉拉队排练
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档