首页 > 其他分享 >Manacher与exKMP(扩展KMP,Z函数)

Manacher与exKMP(扩展KMP,Z函数)

时间:2023-12-21 19:26:46浏览次数:34  
标签:exKMP 函数 int Manacher mid len KMP 区间 回文

Manacher 算法

该算法由 Glenn K. Manacher 在 1975 年提出,首先注意到回文串的对称中心特性可能有所不同(中心可能为一个字符或者是在两个字符之间),那么我们将字母之间插入隔板,这两个回文串的对称中心就都在一个字符上了,such as "|A|B|B|A|"、"|A|B|C|B|A|"

过程

对于一个回文串,有且仅有一个对称中心,我们称之为回文对称中心

在一个回文串内,任选一段区间 \(X\),一定存在关于“回文对称中心”对称的一个区间 \(Y\),我们把区间 \(Y\) 叫关于区间 \(X\) 的对称区间。

可得:

  • 区间和对称区间一定全等。
  • 若一个区间的对称区间是回文串,这个区间必定是一个回文串。在大的回文串内(即不考虑其他字符)它们回文半径相等
  • 我们通过确定关系预先得到的回文半径的数值必定小于等于这个位置真实的回文半径。

因此,我们若记录以每个位置为中心的的回文串半径,我们可以通过另一个已确认的回文串中心把未确认的中心点对应过去提前赋值,这样可以帮助我们减少判断次数,优化时间复杂度。

假设目前未确认点为 \(i\),已确认的且覆盖点 \(i\) 的回文串中心、半径分别为 \(mid\)、\(r\),那么点 \(i\) 的对称点则为 \(j=2\times mid-i\),那么 \(j\leq mid \leq i\),点 \(j\) 作为回文中心的最大回文半径 \(r'\) 已经求得,我们可以预先确定一部分回文串,将点 \(i\) 初值赋为 \(\min(r', mid+r-i)\),至于为什么取 \(\min\),因为大回文串右端点为 \(mid+r-1\),点 \(i\) 初赋值不能判断到大区间外面的点,那么我们每个点肯定会从右端点最靠右的回文串赋值过来,每次我们存一下右端点最靠右的回文串的中心、半径即可,如果点 \(i\) 赋值为 \(mid+r-i\) 了,我们就可以扩展半径判断是否可行(可以感性理解一下)

按照这样做,每个字符只会被循环访问到一次,所以时间复杂度为 \(O(n)\),代码如下:

#include <bits/stdc++.h>
using namespace std;
int n, len[22000005], mid, mr, ans;
char s[11000005], t[22000005];
int main() {
	scanf("%s", s+1);
	n=strlen(s+1);
	for(int i = 1; i <= n; ++i) t[i*2]=s[i], t[i*2-1]='#';/*中间填上字符*/
	t[2*n+1]=t[0]='#', len[0]=len[1]=1, mid=1, mr=2;
	for(int i = 2; i <= 2*n+1; ++i) {
		if(i >= mr) len[i]=1;//比最右边还右边就直接赋值1
		else len[i]=min(len[mid*2-i], mr-i);//从之前那里继承一点从而优化时间复杂度
		while(t[i+len[i]] == t[i-len[i]]) ++len[i];//暴力扩展
		if(i+len[i] > mr) {//更新
			mr=i+len[i];
			mid=i;
		}
	}
	for(int i = 1; i <= 2*n+1; ++i)
		ans=max(ans, (len[i]*2-1)/2);
	printf("%d\n", ans);
	return 0;
} 

扩展 KMP/exKMP(Z 函数)

Z 函数,又称扩展 KMP (exkmp),可以 \(O(n)\) 求出一个字符串的所有后缀与这个字符串的 LCP 长度。

怎么叫做扩展 KMP 但是前置知识没有 KMP,Z 函数的做法与 Manacher 有着异曲同工之妙,即存下了目前已扩展到的右端点最靠右端的后缀 \(i\) 与原串的 LCP:\([i,i+Z[i]-1]\),可以发现,当 \(i<j\leq i+Z[i]-1\) 时,字符串 \([j,i+Z[i]-1]\) 与 \([j-i+1,z[i]]\)(相当于将 \([1,Z[i]]\)、\([i,i+Z[i]-1]\) 按 \(j\) 对应的位置切开取了右半边) 一定是相同的,那么 \(Z[j]\) 就可以直接初赋值上 \(\min\{Z[j-i+1],i+Z[i]-j\}\),这样就能保证每个字符只会被扩展到 \(1\) 次,因此总时间复杂度是 \(O(n)\) 的(纯意识流是因为懒得画图嘿嘿)

于是我们可以在 \(O(|a|+|b|)\) 的时间中求模式串 \(b\) 与另一个字符串 \(a\) 所有后缀的 LCP 长度,具体的,可以先求出 \(b\) 的 Z 函数后参照求 Z 函数的思路求 \(a\) 的所有后缀与 \(b\) 的 LCP,也可以将 \(b\)、\(a\) 拼在一起后求一次 Z 函数,模板代码如下:(此处只展示第一种先求 \(b\) 的 Z 函数的做法 QwQ,毕竟会求 Z 函数就会第二种求法了)

#include <bits/stdc++.h>
using namespace std;
const int N = 2e7+5;
int n, m, z[N], p[N];
char a[N], b[N];

template <typename T> void read(T& x) {
	x = 0; int f = 0; char c = getchar();
	while(c < '0' || c > '9') f |= (c == '-'), c=getchar();
	while(c >= '0' && c <= '9') x=(x<<1)+(x<<3)+(c^48), c=getchar();
	x=(f ? -x : x);
}
int lne; char put[105];
template <typename T> void write(T x, char ch) {
	lne = 0; if(x < 0) putchar('-'), x=-x;
	do { put[++lne]=x%10, x/=10; } while(x);
	while(lne) putchar(put[lne--]^48);
	putchar(ch);
}

signed main() {
	scanf("%s%s", a+1, b+1);
    n=strlen(a+1), m=strlen(b+1);
    //求Z函数:
    z[1]=m;//初赋值
    for(int i = 2, l = 0, r = 0; i <= m; ++i) {
        if(i <= r) z[i]=min(z[i-l+1], r-i+1);//与Manacher异曲同工之处
        while(i+z[i] <= m && b[i+z[i]] == b[z[i]+1]) ++z[i];//向右扩展没有被扩展过的位置
        if(i+z[i]-1 > r) r=i+z[i]-1, l=i;//更新
    }
    //求a的后缀与b的LCP长度:
    for(int i = 1/*从1开始,求Z函数时则不能从1开始*/, l = 0, r = 0; i <= n; ++i) {
        if(i <= r) p[i]=min(z[i-l+1], r-i+1);//是对b求得的Z函数取min!一定要注意
        while(i+p[i] <= n && a[i+p[i]] == b[p[i]+1]) ++p[i];//a与b比较
        if(i+p[i]-1 > r) r=i+p[i]-1, l=i;//更新
    }
    long long ans1 = 0, ans2 = 0;
    for(int i = 1; i <= m; ++i)
        ans1^=1LL*i*(z[i]+1);
    for(int i = 1; i <= n; ++i)
        ans2^=1LL*i*(p[i]+1);
    write(ans1, '\n');
    write(ans2, '\n');
	return 0;
}

看着似乎用处不大可以被二分加哈希代替但是在有时不能二分哈希或者卡常的时候还是很有用的

标签:exKMP,函数,int,Manacher,mid,len,KMP,区间,回文
From: https://www.cnblogs.com/RJlalala/p/Manacher-and-exKMP.html

相关文章

  • 扩展 KMP/exKMP(Z 函数)
    模板链接QwQZ函数,又称扩展KMP(exkmp),可以\(O(n)\)求出一个字符串的所有后缀与这个字符串的LCP长度。怎么叫做扩展KMP但是前置知识没有KMP,Z函数的做法与Manacher有着异曲同工之妙,即存下了目前已扩展到的右端点最靠右端的后缀\(i\)与原串的LCP:\([i,i+Z[i]-1]\)......
  • KMP算法
    用于解决字符串匹配问题名词解释前缀表前缀:包含首字母不包含尾字母的所有子串比如aabaaf的前缀有aaaaabaabaaabaa后缀:包含尾字母不包含首字母的所有子串比如aabaaf的后缀有fafaafbaafabaaf最长相等前后缀:比如aabaa,最长的,相等的,前缀和后缀相等,那么这个缀......
  • KMP算法和Manacher算法
    KMP算法KMP算法解决的问题KMP算法用来解决字符串匹配问题:找到长串中短串出现的位置.KMP算法思路暴力比较与KMP的区别暴力匹配:对长串的每个位,都从头开始匹配短串的所有位.KMP算法:将短字符串前后相同的部分存储在\(next\)数组里,让之前匹配过的信息指导之后的匹配.......
  • KMP & ACAM
    KMP例:在a串中查找b串的位置。(len<=1e6)O(n2)的暴力是好想的。两层循环,第一层遍历a串,第二层遍历b串,对应比较即可。但我们会发现对于a串,我们每次都不断将循环变量i右移,可匹配失败后,又将i返回至右移之前的位置。太劣了,所以我们选择取消i的返回操作,每次用b串去匹配a串。但当b失......
  • 【kmp算法】字符串匹配
    一,解决问题kmp算法解决的是字符串匹配的问题,具体来说假定我们要在主串s[]中匹配模式串p[],找到匹配到的位置loc;二,具体实现和演变过程最自然的想法是暴力写法(BF)枚举主串字符s[i],和模式串p[j]。一个一个匹配,如果匹配失败,i指针回退回起点,往前进一位,再次进行比较,......
  • KMP 学习笔记
    符号规定先来规定一些符号。\(\lvertS\rvert\)代表这个字符串\(S\)的长度。\(S_{l\cdotsr}\)代表字符串从第\(l\)个字符到第\(r\)个字符组成的字串。\(F(S,i)\)等同于\(S_{1\cdotsi}\)(就是字符串长度为\(i\)的前缀)\(E(S,i)\)等同于\(S_{\lvertS\rvert-i+1......
  • KMP
    KMP算法实现KMP串匹配主要分为两个步骤,即获得match数组(或者说next数组),然后应用match数组来进行串匹配的简化获取match数组KMP的精髓就在于使用match数组使得i指针不需回退,使得暴力的m*n的时间复杂度变为m+n的时间复杂度,其中的m指的就是求match数组的复杂度。match数组......
  • KMP算法
    1.暴力匹配暴力匹配算法的步骤如下:遍历主串中的每个可能的起始位置,从第一个字符开始。对于每个起始位置,逐个比较主串和模式串中对应位置的字符。如果发现不匹配的字符,即主串和模式串中对应位置的字符不相等,将模式串向右移动一个位置,继续比较。如果模式串完全匹配主串中的一......
  • KMP
    简介KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。实现构造next[]数组前缀:除最后一个字符外,......
  • 关于kmp模板
    那个求p串的next数组这个版本是下标从1开始的字符串,如果从0开始的话,可以在前面加空字符,然后p.size或者s.size的地方-1即可。nex[1]=0    for(inti=2,j=0;i<=p.size();i++){if(j&&p[i]!=p[j+1])j=nex[j];if(p[i]==p[j+1])j++;nex[i]=j;} kmp函数......