Manacher&KMP算法

Manacher算法

Manacher模板

左程云:Manacher算法

洛谷:P3805【模板】manacher

题目描述

给出一个只由小写英文字符 a,b,c,…y,z 组成的字符串 S ,求 S 中最长回文串的长度 。
字符串长度为 n。

输入格式

一行小写英文字符 a,b,c,⋯,y,z 组成的字符串 S。

输出格式

一个整数表示答案。

分析

1st. 一般性思路:对于给定的每一个字符s[i],都搜一遍 --> O(n^2)

2nd. 通过记忆划,将每一次求出的答案储存,再根据亿点点Manacher分析 --> O(n)

3rd. Manacher

  1. 加速过程

  1. 时间复杂度分析:
    易证:c,r,len均只升不降,且r的变化与c,len相关,可以得到r变化的时间复杂度即为Manacher的时间复杂度 --> O(n)

代码

#include "bits/stdc++.h"
using namespace std;
const int N=1e7+5;
int n,l[5*N],ans=-1;
string a="#";
/*a[i]有:
它被中心位置为cr的回文子串包含;
回文半径右边界;
  -> r一直++,直至r=n-1,时间复杂度为O(n)
以它为中心的回文子串半径为len;*/
void Manacher(){
    int c=0,r=0,len=0;
	for(int i=0;i<n;i++){
        //如果在r以内,len直接=其以c的对称点的len';否则,最小为1
		len= r>i?min(l[2*c-i],r-i):1;
		//最普通的,直接while求回文半径len
		while(i+len<n&&i-len>=0&&a[i+len]==a[i-len])
			len++;
		//如果半径超过了r,更新
		if(i+len>r){
			r=i+len;
			c=i;
		}
        //存答案
		l[i]=len;
		ans=max(ans,len);
	}
    return ;
}
int main(){
	char x;
	while(cin>>x)a+=x,a+="#";
	n=a.length();
	Manacher();
	cout<<ans-1;
	return 0;
}

扩展KMP

左程云:扩展KMP

洛谷:P5410【模板】扩展 KMP/exKMP(Z 函数)

题目描述

给定两个字符串 a,b,你要求出两个数组:
b 的 z 函数数组 z,即 b 与 b 的每一个后缀的 LCP 长度。
b 与 a 的每一个后缀的 LCP 长度数组 p。

对于一个长度为 n 的数组 a,设其权值为 xori=1n​i×(ai​+1)。

输入格式

两行两个字符串 a,b。

输出格式

第一行一个整数,表示 z 的权值。
第二行一个整数,表示 p 的权值。

分析

1st. 与Manacher类似,旦不是KMP!

2nd. 扩展KMP

  1. z数组记字符串与其后缀的最长公共前缀长度。

  2. e数组记最长可扩展后缀长度。

  3. 加速过程

  4. 时间复杂度分析:同Manacher -> O(n)

代码

#include "bits/stdc++.h"
#define ll long long
using namespace std;
const int N=2*1e7+5;
long long z[N],e[N],xz=0,xe=0;
string a,b;
void zArray(string a,int n){
    z[0]=n;
    ll c=1,r=1,len;
    for(int i=1;i<n;i++){
        len= r>i?min(r-i,z[i-c]):0;
        while(i+len<n&&a[i+len]==a[len])
            len++;
        if(i+len>r){
            r=i+len;
            c=i;
        }
        z[i]=len;
    }
    return ;
}
void eArray(string a,string b,int n,int m){
    ll c=0,r=0,len;
    for(int i=0;i<n;i++){
        len= r>i?min(r-i,z[i-c]):0;
        while(i+len<n&&len<m&&a[i+len]==b[len])
            len++;
        if(i+len>r){
            r=i+len;
            c=i;
        }
        e[i]=len;
    }
    return ;
}
int main(){
    cin>>a>>b;
    zArray(b,b.length());
    eArray(a,b,a.length(),b.length());
    for(int i=0;i<b.length();i++)
        xz^=(i+1)*(z[i]+1);
    for(int i=0;i<a.length();i++)
        xe^=(i+1)*(e[i]+1);
    cout<<xz<<endl<<xe;
    return 0;
}

KMP算法

左程云:KMP算法

洛谷:P3375【模板】KMP

题目描述

给出两个字符串 s1​ 和 s2​,若 s1​ 的区间 [l,r] 子串与 s2​ 完全相同,则称 s2​ 在 s1​ 中出现了,其出现位置为 l。
现在请你求出 s2​ 在 s1​ 中所有出现的位置。

定义一个字符串 s 的 border 为 s 的一个非 s 本身的子串 t,满足 t 既是 s 的前缀,又是 s 的后缀。
对于 s2​,你还需要求出对于其每个前缀 s′ 的最长 border t′ 的长度。

输入格式

第一行为一个字符串,即为 s1​。
第二行为一个字符串,即为 s2​。

输出格式

首先输出若干行,每行一个整数,按从小到大的顺序输出 s2​ 在 s1​ 中出现的位置。
最后一行输出 ∣s2​∣ 个整数,第 i 个整数表示 s2​ 的长度为 i 的前缀的最长 border 长度。

分析

1st. 参考Manacher,目标将时间复杂度:O(n*m) --> O(n+m)

2nd. KMP

  1. 首先求出next数组记模式字符串的前缀和后缀的最长公共元素长度 --> O(m)
  2. 加速过程(同Manacher) --> O(n)

(1) next数组跳跃过程:

I. 重点在每个点回溯上一个点的next值;
II. 以及判断该点与前缀的终止点的值是否相等。

(2) 匹配主流程:

I. 如果能对上,继续;
II. 如果对不上了,查找next数组达到移动模式字符串的目的。

3rd. by Deepseek R1:

KMP算法中的next数组是一个核心组件,用于记录模式字符串的前缀和后缀的最长公共元素长度,以便在主字符串中进行高效的模式匹配‌‌。

next数组的定义和作用

next数组记录了模式字符串的前缀和后缀的最长公共元素长度。在模式匹配过程中,当发生不匹配时,next数组可以帮助确定模式字符串应该滑动多远,以跳过已经匹配过的部分,从而减少不必要的匹配尝试,提高匹配效率‌。

next数组的计算方法

计算next数组的过程是一个递推的过程:

    初始化:next = -1,表示没有相匹配的前后缀;next‌1 = 0,因为单个字符没有前缀和后缀。
    递推计算:对于每个位置j(j > 1),通过比较模式串的第j个字符和第next[j-1]个字符是否相等来确定next[j]的值。如果相等,则next[j] = next[j-1] + 1;如果不相等,则需要回退到更短的相同前后缀,直到找到匹配的前后缀或回退到-1为止‌。

next数组在KMP算法中的应用

在KMP算法中,next数组通过记录模式字符串的前缀和后缀的最长公共元素长度,帮助算法在匹配失败时确定模式字符串的滑动距离。

代码

//左程云版,70分
#include "bits/stdc++.h"
using namespace std;
const int N=1e6+5;
string a,b;
int la,lb,n[N];
void nArray(){
    int j=0;
    for(int i=2;i<=lb;i++){
        while(j&&b[i]!=b[j])j=n[j];
        if(b[i]==b[j])j++;
        n[i]=j;
    }
    return ;
}
void KMP(){
    int j=0;
    for(int i=0;i<la;i++){
        int x=i;
        while(a[x]==b[j])x++,j++;
        if(j==lb){
            cout<<i+1<<endl;
            j=n[j];
        }
    }
    return ;
}
int main(){
    cin>>a>>b;
    la=a.length(),lb=b.length();
    nArray();
    KMP();
    for(int i=0;i<lb;i++)cout<<n[i]<<" ";
    return 0;
}
//@皎月半洒花 的题解,100分
#include<iostream>
#include<cstring>
#define MAXN 1000010
using namespace std;
int kmp[MAXN];
int la,lb,j; 
char a[MAXN],b[MAXN];
int main()
{
    cin>>a+1;
    cin>>b+1;
    la=strlen(a+1);
    lb=strlen(b+1);
    for (int i=2;i<=lb;i++)
	   {     
	   while(j&&b[i]!=b[j+1])
        j=kmp[j];    
       if(b[j+1]==b[i])j++;    
        kmp[i]=j;
       }
    j=0;
    for(int i=1;i<=la;i++)
	   {
          while(j>0&&b[j+1]!=a[i])
           j=kmp[j];
          if (b[j+1]==a[i]) 
           j++;
          if (j==lb) {cout<<i-lb+1<<endl;j=kmp[j];}
       }

    for (int i=1;i<=lb;i++)
    cout<<kmp[i]<<" ";
    return 0;
}

文献参考:哔站:左程云

the manuscript of teacher

(老师的手稿)

posted on 2025-05-09 21:00  小黄同学01  阅读(53)  评论(0)    收藏  举报

导航