首页 > 编程语言 >单模字符串匹配算法(KMP, exKMP, manacher)

单模字符串匹配算法(KMP, exKMP, manacher)

时间:2023-06-17 11:44:19浏览次数:44  
标签:exKMP include 前缀 int manacher cdots pi 单模 函数

约定:本文字符串均从 \(1\) 开始。模式串 \(T\) 的长度为 \(n\),匹配串 \(S\) 的长度为 \(m\)。

1. KMP

1.1 前缀函数

给定一个长度为 \(n\) 的字符串 \(S\),其前缀函数被定义为一个长度为 \(n\) 的数组 \(\pi\)。其中 \(\pi_i\) 被定义为:

  1. 若子串 \(S[1\cdots i]\) 有一对相等的真前缀与真后缀(即除了该子串本身的前缀和后缀)则 \(\pi_i=i\),即为该真前缀的长度;
  2. 如果不只有一个相等的,那么 \(\pi_i\) 就是其中最长的那一对的长度;
  3. 如果没有相等的,那么 \(\pi_i=0\)。

劝退地,

\[\pi_i=\max_{0\le k<i}\{ k : S[1\cdots k] = S[i-k+1\cdots i]\} \]

特别地,\(\pi_1=0\)。

1.2 计算前缀函数

考虑如何计算 \(\pi(T)\)。

假设我们已经求出了 \(\pi_1\sim\pi_{i-1}\),令 \(\pi_{i-1}=j\),若 \(T[j+1]=T[i]\),根据前缀函数的定义,可以得到 \(T[1\cdots j+1]=T[i-j\cdots 1]\),则 \(\pi_i=j+1\);否则,\(j\leftarrow \pi_j\),继续判断直到 \(j=0\),此时 \(\pi_i=0\)。

1.3 KMP 匹配

得到模式串 \(T\) 的前缀函数后,可以用与之类似的方式将其与 \(S\) 进行匹配:假设已经匹配完 \(S[i-1]\),对应的 \(T\) 中的字符为 \(T[j]\),若 \(S[i]=T[j+1]\),则可以继续匹配;否则 \(j\leftarrow \pi_j\),根据前缀函数的定义可得 \(T[\pi_j]=T[j]=S[i-1]\),继续判断直到 \(j=0\),此时从头开始匹配。

若 \(j=n\),说明匹配完成,则与之第一个匹配的位置为 \(S[i-n+1]\)。

时间复杂度 \(O(n+m)\)。

P3375 【模板】KMP字符串匹配

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 1e6+10;

char T[N], S[N];
int n, m, ne[N]; // ne为T的前缀函数 

void get_next() { // 求ne数组(即前缀函数) 
	ne[1] = 0;
	for (int i = 2, j = 0; i <= n; ++i) {
		while (T[j+1] != T[i] && j) j = ne[j];
		if (T[j+1] == T[i]) ne[i] = ++j;
	}
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	
	cin >> S+1 >> T+1;
	n = strlen(T+1), m = strlen(S+1);

	get_next();
	
	for (int i = 1, j = 0; i <= m; ++i) { // 匹配 
		while (T[j+1] != S[i] && j) j = ne[j];
		if (T[j+1] == S[i]) ++j;
		if (j == n) cout << i-n+1 << '\n', j = ne[j];
	}

	for (int i = 1; i <= n; ++i) cout << ne[i] << ' ';
	cout << '\n';
	return 0;
}

2. exKMP

2.1 Z 函数

给定一个长度为 \(n\) 的字符串 \(S\),定义其 Z 函数 \(z(i)\) 表示 \(S\) 和 \(S[i,n]\)(即以 \(S[i]\) 开头的后缀)的最长公共前缀(LCP)的长度。

劝退地,

\[z(i)=\max_{0\le k\le n-i+1}\{k:s[1\cdots k]=s[i\cdots i+k-1]\} \]

特别地,\(z(1)=n\)。

2.2 计算 Z 函数

考虑如何计算 \(T\) 的 Z 函数。

对于 \(i\),我们称区间 \([i,i+z(i)-1]\) 为 \(i\) 的匹配段,也称作 Z-box。我们需要维护最靠右的 box,记作 \([l,r]\),初始时 \(l=r=0\)。根据定义,可得 \(s[l\cdots r]=s[1\cdots r-l+1]\)。

假设我们已经求出了 \(z(1)\sim z(i-1)\)。则:

  1. 若 \(i\le r\):根据 \([l,r]\) 的定义,有 \(s[i\cdots r]=s[i-l+1\cdots r-l+1]\),因此 \(z(i)\ge \min(z(i-l+1),r-i+1)\)。这时:
  • \(z(i-l+1)<r-i+1\):即 \(i+z(i)-1\) 对应的位置在 box 内,则 \(z(i)=z(i-l+1)\)。
  • \(z(i-l+1)\ge r-i+1\):即 \(i+z(i)-1\) 对应的位置在 box 外,则 \(z(i)\leftarrow z(i-l+1)\),然后暴力枚举能够扩展的字符即可。
  1. 若 \(i>r\):暴力枚举能够扩展的字符。

计算完 \(z(i)\) 后更新 \(l,r\)。

2.3 exKMP 匹配

与计算 Z 函数的过程类似,但我们是在 \(S\) 上计算。

P5410 【模板】扩展 KMP(Z 函数)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long ll;

const int N = 2e7+10;

char T[N], S[N];
int n, m, z[N], p[N]; 

ll get_num(int a[], int n) {
	ll ans = 0;
	for (int i = 1; i <= n; ++i) ans ^= (ll)i * (a[i]+1);
	return ans;
}

void get_z() { // 计算z函数
	z[1] = n;
	for (int i = 2, l = 0, r = 0; i <= n; ++i) {
		if (i < r) z[i] = min(z[i-l+1], r-i+1);
		while (z[i] <= n-i && T[i+z[i]] == T[1+z[i]]) z[i] ++;
		if (i+z[i]-1 > r) r = i+z[i]-1, l = i;
	} 
}

void get_p() { // 计算p函数
	for (int i = 1, l = 0, r = 0; i <= m; ++i) {
		if (i < r) p[i] = min(z[i-l+1], r-i+1);
		while (p[i] <= m-i && S[i+p[i]] == T[1+p[i]]) p[i] ++;
		if (i+p[i]-1 > r) r = i+p[i]-1, l = i;
	}
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	
	cin >> S+1 >> T+1;
	n = strlen(T+1), m = strlen(S+1);
	
	get_z(), get_p();
	
	cout << get_num(z, n) << '\n' << get_num(p, m) << '\n';
	return 0;
}

标签:exKMP,include,前缀,int,manacher,cdots,pi,单模,函数
From: https://www.cnblogs.com/Jasper08/p/17487113.html

相关文章

  • z函数|exkmp|拓展kmp 笔记+图解
    题外话,我找个什么时间把kmp也加一下图解z函数|exkmp别担心这个exkmp和kmp没毛点关系,请放心食用。本文下标以1开始,为什么?因为1开始就不需要进行长度和下标的转换,长度即下标。定义给出模板串S和子串T,长度分别为n和m,对于每个ans[i](1<=i<=n),求出S[i...n]与T的最长公共前缀长......
  • hdu 5157(manacher+前缀和+树状数组)
    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5157解题思路:我们可以先用mancher算法对字符串进行处理,把以每个点为中心的回文串半径求出来,然后进行处理。加入对以p为中心的点,从p-r[i]+1~p都是回文串的开头,那么对于每个回文串(开头是j)只要记录结尾从1~j-1的回文串个数,我们可......
  • libtorch教程(三)简单模型搭建
    前言 模块化编程的思想非常重要,通过模块化编程可以大幅减少重复的敲代码过程,同时代码可读性也会增加。本章将讲述如何使用libtorch搭建一些MLP和CNN的基本模块。本教程禁止转载。同时,本教程来自知识星球【CV技术指南】更多技术教程,可加入星球学习。欢迎关注公众号CV技术指南,专......
  • Manacher
    Manacher的几个模板模板一前后插入不等的特殊字符(不用写越界的判断条件)中间用#隔离(不用判断奇偶)#include<bits/stdc++.h>usingnamespacestd;constintN=22000005;chars[N],t[N];intcnt,mr,mid,len[N];voidbuild(){ cin>>s; t[++cnt]......
  • JAVA快速开发框架 一键生成表单模板代码
    从计算机诞生开始,虽然编程的形式随着硬件及软件的不断进步而不停迭代,但是从事计算机技术行业的人员始终与编写代码的任务紧密联系在一起。因此如何提高软件开发的效率和质量,一直是软件工程领域的重要问题之一。这一方面是由于在不同软件开发过程中存在大量相似代码复用的情况,多次......
  • JAVA快速开发框架 一键生成表单模板代码
    从计算机诞生开始,虽然编程的形式随着硬件及软件的不断进步而不停迭代,但是从事计算机技术行业的人员始终与编写代码的任务紧密联系在一起。因此如何提高软件开发的效率和质量,一直是软件工程领域的重要问题之一。这一方面是由于在不同软件开发过程中存在大量相似代码复用的情况,多次编......
  • Python多线程爬虫简单模板
    多线程爬虫的流程可以大致分为:(1)获取种子URL:从初始URL中抓取起始页面,解析其中的URL,并将这些URL添加到未访问的URL队列中;(2)解析下载的网页:从URL队列中取出一个URL,下载其内容,解析其中的链接,并把新的链接放入未访问的URL队列中;(3)存储爬取的数据:从URL队列中取出未访问的URL,把其中的内......
  • codeforces 159D D. Palindrome pairs( manacher+dp )
    题目链接:codeforces159D题目大意:给出一个字符出,求取这个字符串中互相不覆盖的两个回文子串的对数。题目分析:首先能够用manacher模板,因为这个算法处理的字符串的长度式奇数,所以我们首先将原字符串拓展,也就是用一个没有出现过的子串填充到每两个字符之间,首位也要添加,这样处理后得到......
  • 马拉车(manacher) & 回文自动机(PAM)
    读了徐安矣2023年集训队论文写的,对于差分性质和习题,我会在理解清楚之后再补充。本篇博客仅讨论前两种算法。首先,马拉车和回文自动机都是处理回文串问题的。但在此之前,学习一些更加简单的回文算法。小trick:把给定串的两头和缝隙插入相同字符,且在边界处用不同字符标记,使得长度为......
  • kmp + exkmp
    kmp:主要就是用于暴力回退的优化一般的暴力回退总是回退到前一个,要枚举很多次如果找到规律那么就会发现可以找到上一次最大匹配的位置然后将继续匹配知道匹配不下去然后去更新代码kmp是前缀到某一个为停止#include<bits/stdc++.h>usingnamespacestd;intn,m;intnx......