首页 > 其他分享 >Manacher

Manacher

时间:2024-02-14 17:55:46浏览次数:29  
标签:int Manacher len 最长 maxn mx 回文

Manacher

Manacher(马拉车)能在 O(n) 的时间复杂度内求出字符串中以字符i为中心的最长回文半径r[i]

算法流程

预处理

为了避免奇偶性问题

在字符串开始加上奇怪的 @

在字符串末尾加上&

在字符中间加上 #

本段代码来自可爱的w++
并且把他老婆inline删掉了

void Init() {
    scanf("%s", s1 + 1);
    int len = strlen(s1 + 1);

    //构建新串
    n = 0;
    s[++n] = '#';
    for (int i = 1; i <= len; i++) {
        s[++n] = s1[i];
        s[++n] = '#';
    }
    s[0] = '@';
    s[n + 1] = '&';
}

求r数组

两个辅助变量mx,p

mx表示当前覆盖到的最右边界

p表示当前的中心位置

显然mx=p+r[p]

计算时 我们先求出r[i]的下界(最小值)

然后 直接向左右暴力扩展

j为i关于p的对称点
j=p*2-i(中点坐标公式)

求i的下界时 有三种情况

  1. mx<=i (完全不覆盖) r[i]=1

image

  1. mx-i>r[j] (完全覆盖)r[i]=r[j] 因为i和j是对称的

image

  1. mx-i<=r[j] (部分覆盖) r[i]>=mx-i

image

算完r[i]的下界后暴力向左右扩展即可


时间复杂度

因为mx必只向右移动n次

枚举i也是n次

所以总复杂度为O(n)级别

代码

void manacher()
{
    int p=0,mx=0;
    for(int i=1;i<=n;i++)
    {
        if(i<mx) r[i]=min(mx-i,r[p*2-i]);//第2 3种情况合并一下
        else r[i]=1;//第1种情况
        while(s[i-r[i]]==s[i+r[i]]) ++r[i];//向左右扩展
        if(i+r[i]>mx)
        {
            mx=r[i]+i;
            p=i;
        }
    }
}

题目

P4555 [国家集训队] 最长双回文串

//维护最长回文半径的同时,
//再分别维护两个东西,
//以i为结尾的最长回文子串的长度l[i]
//和以i为开头的最长回文子串的长度r[i]
//因为每个双回文串中间不能交叉
//即i只能是‘#’
//所以只能枚举i=='#'来找答案
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+100;
int n,len[(N<<1)+10],l[(N<<1)+10],r[(N<<1)+10];
char ch[N+10],s[(N<<1)+10]; //原数组 改后数组
void manacher()
{
    int p=0,mx=0;
    for(int i=1;i<=n;i++)
    {
        if(i<mx) len[i]=min(mx-i,len[p*2-i]);//第2 3种情况合并一下
        else len[i]=1;//第1种情况
        while(s[i-len[i]]==s[i+len[i]]) ++len[i];//向左右扩展
        if(i+len[i]>mx)
        {
            mx=len[i]+i;
            p=i;
        }
        l[i+len[i]-1]=max(l[i+len[i]-1],len[i]-1);//回文串真实长度为len[i]-1
		r[i-len[i]+1]=max(r[i-len[i]+1],len[i]-1);
		//这里只处理了以i为中心的最长回文串 所以之后要再处理l r数组
    }
}

int main()
{
    scanf("%s",ch+1);
    int tlen=strlen(ch+1);
    s[0]='$';
    s[1]='#';
    n=1;
    for(int i=1;i<=tlen;++i)
	{
		s[++n]=ch[i];
		s[++n]='#';
	}
	manacher();
	for(int i=3;i<=n;i+=2)r[i]=max(r[i],r[i-2]-2);
	//+=2是因为枚举的是# 中间差2 每差一个# 原串的长度也少2(左右各一个)所以-2
	for(int i=n;i>=3;i-=2)l[i]=max(l[i],l[i+2]-2);
	int ans=0;
	for(int i=3;i<=n;i+=2)if(r[i]&&l[i])ans=max(ans,l[i]+r[i]);//要写r[i]&&l[i]
	printf("%d\n",ans);
    return 0;
}

字符串连接—YbtOJ

manacher求回文串,后得到线段,做一点计算映射回原串线段。

然后问题转化为可重叠区间线段覆盖问题,可以贪心解决。

排序左端点,同一左端点取最长段,然后在此段中找到右端点最靠右的线段,线性更新并累加。

#include<bits/stdc++.h>
using namespace std;
const int maxn=200010;
int n,p[maxn];
char s[maxn],ss[maxn];
struct cyc
{
    int l,r;
} a[maxn];
bool cmp(cyc a,cyc b)
{
    return a.l<b.l||(a.l==b.l&&a.r>b.r);
}
void manacher()
{
    memset(p,0,4*(n+1));
    int id=0,mx=0;
    for(int i=1; i<=n; i++)
    {
        if(mx>i)
            p[i]=min(p[id*2-1],mx-i+1);
        else p[i]=1;
        while(s[i+p[i]]==s[i-p[i]]) p[i]++;
        if(i+p[i]-1>mx)
        {
            mx=i+p[i]-1;
            id=i;
        }
        if(i%2) a[i].l=i/2-p[i]/2+1,a[i].r=i/2+p[i]/2;
        else a[i].l=i/2-p[i]/2+1,a[i].r=i/2+p[i]/2-1;
        if(a[i].l>a[i].r)
            a[i].l=a[i].r=1;
    }
}
int main()
{
    while(scanf("%s",ss+1)==1)
    {
        int tot=strlen(ss+1);
        n=1;
        s[0]='$';
        s[1]='#';
        for(int i=1; i<=tot; i++)s[++n]=ss[i],s[++n]='#';
        manacher();
        sort(a+1,a+n+1,cmp);
        int right=a[1].r,ans=1,mx=0;
        for(int i=2; i<=n; i++)
            if(a[i].l!=a[i-1].l)
            {
                if(a[i].l>right+1)ans++,right=mx;
                mx=max(mx,a[i].r);
            }
        if(right<tot)ans++;
        printf("%d\n",ans-1);
    }
    return 0;
}

标签:int,Manacher,len,最长,maxn,mx,回文
From: https://www.cnblogs.com/zysssss/p/18015376

相关文章

  • manacher
    记录23:392024-2-5manacher算法,是可以在O(n)时间计算回文串的算法具体思路可以查看Manacher非常有意思的算法。利用了俩个数组d1[i]和d2[i]分别来记录以位置i为中心的长度为奇数和长度为偶数的回文串个数这里利用了回文串个数也即以i为中心的最长回文串的半径长度(半......
  • manacher 学习笔记
    定义与基本求法定义又名马拉车,用于处理子串回文问题。基本求法暴力判断每个子串是否是回文是\(O(n^3)\)的,根据其对称性优化为\(O(n^2)\)依旧是不优秀的。马拉车便是解决这种单一问题的算法,具有局限性,但同时是解决这种问题的不二选择。枚举回文串的中点,例如\(aaba......
  • 最小表示法&Manacher学习笔记+杂题
    字符串系列前言:孩子从小就自卑。四、最小表示法&Manacher学习笔记+杂题相关题单:戳我1.最小表示法最小表示法是用于解决字符串最小表示问题的方法。(1)字符串的最小表示:字符串\(s\)的最小表示为与\(s\)循环同构的所有字符串中字典序最小的字符串。循环同构指的是当字符......
  • 最小表示法&Manacher学习笔记+杂题
    字符串系列前言:孩子从小就自卑。四、最小表示法&Manacher学习笔记+杂题相关题单:戳我1.最小表示法最小表示法是用于解决字符串最小表示问题的方法。(1)字符串的最小表示:字符串\(s\)的最小表示为与\(s\)循环同构的所有字符串中字典序最小的字符串。循环同构指的是当字符......
  • 【学习笔记】manacher
    众所周知,manacher又叫“马拉车”算法,是一种线性求解最长回文子串的算法。推荐结合模板阅读此文。求最长回文子串,首先想到的是暴力。枚举子串的左右端点\(l,r\),再判断这个子串是否回文。总复杂度\(O(n^3)\),效率过低。观察发现,我们可以只枚举中点,然后同时向左右不断扩展,当无......
  • 高铁拉我,马拉车——记高铁路上的manacher
    目录前言问题引入思路一览manacher高效的原因具体情况讨论小问题的讨论code前言不得为什么,总会在奇奇怪怪的时候特定时间看算法比平常看得舒服多了,之前看字符串匹配的时候自然是准备把马拉车一起看了的,但是那时候看不下去,昨天回家的高铁上再次看了看,觉得格外的亲切,emmm问题引入......
  • 【CF30E】Tricky and Clever Password 题解(manacher + exKMP)
    manacher+exKMP+二分。感觉是最粗暴的方法,想出来之后自己硬莽了4k,荣获题解区最长。Solution约定:下文所提及到的所有的回文串,均指奇长度回文串。显然把题目拆成两个部分,中间的回文串,以及两边相同的连续子串。考虑一下从哪个入手比较好。忘记是咋想的了,易得从两边相同......
  • Manacher与exKMP(扩展KMP,Z函数)
    Manacher算法该算法由GlennK.Manacher在1975年提出,首先注意到回文串的对称中心特性可能有所不同(中心可能为一个字符或者是在两个字符之间),那么我们将字母之间插入隔板,这两个回文串的对称中心就都在一个字符上了,suchas"|A|B|B|A|"、"|A|B|C|B|A|"过程对于一个回文串,有且......
  • KMP算法和Manacher算法
    KMP算法KMP算法解决的问题KMP算法用来解决字符串匹配问题:找到长串中短串出现的位置.KMP算法思路暴力比较与KMP的区别暴力匹配:对长串的每个位,都从头开始匹配短串的所有位.KMP算法:将短字符串前后相同的部分存储在\(next\)数组里,让之前匹配过的信息指导之后的匹配.......
  • manacher
    \(\mathrm{manacher}\)算法可以在线性时间内求出一个串中的最长回文子串。为了解决偶回文串的中心点非整数,在每个字符之间添加一个字符#。为防止越界问题再在串的前后加上奇怪的符号。记\(mx\)为当前最长回文串的右端,\(id\)为串中心的位置,\(len_{id}\)为以\(id\)为中心......