前缀函数与Z函数介绍
字符串算法果然玄学=_=
参考资料:
OI Wiki:前缀函数与KMP算法
OI Wiki:Z函数(扩展KMP)
0. 约定
字符串的下标从 \(0\) 开始。\(|s|\) 表示字符串 \(s\) 的长度。
对于字符串 \(s\),记其每一个字符分别为 \(s_0, s_1, \cdots, s_{|s|-1}\)。
子串 \(s_l, s_{l+1}, \cdots, s_{r-1}, s_r\) 简记为 \(s[l:r]\)。特别地,若 \(l=0\),可记作 \(s[:r]\);若 \(r=|s|-1\),可记作 \(s[l:]\)。
对于字符串 \(a, b\),\(a+b\) 表示拼接操作,即将字符串 \(b\) 拼接到字符串 \(a\) 之后,构成新的字符串。
记构成的新字符串为 \(c\),则上述拼接操作记为 \(c\gets a+b\)。
其中符号 \(x\gets y\) 表示将 \(y\) 的值赋给 \(x\)。
不论是字符还是字符串,皆不加引号。
1. 前缀函数
1.1. 前缀函数的定义
对于字符串 \(s\),若存在一个非本身的子串 \(t\) 使得 \(t\) 既是 \(s\) 的前缀又是 \(s\) 的后缀,称 \(t\) 为 \(s\) 的一个 \(\text{Border}\)。
更加符号化地,对于一个长为 \(n\) 的字符串 \(s\),若存在 \(m\) 使得 \(m\neq n\) 且 \(s[:m-1]=s[n-m:]\),
称 \(s[:m-1]\) (\(s[n-m:]\) 同理) 为 \(s\) 的一个长度为 \(m\) 的 \(\text{Border}\)。
对于一个字符串 \(s\),定义 \(\text{MaxBorder}(s)\) 为 \(s\) 的 \(\text{Border}\) 中最长的。
接下来定义前缀函数:
对于一个字符串 \(s\),定义前缀函数 \(\pi(s,i)=\left|\text{MaxBorder}(s[:i])\right|\) 。
在不引起歧义的情况下,可简记为 \(\pi(i)\);特别地,定义 \(\pi(s,0)=0\)。
有时我们关心一整个字符串的前缀函数值,故此时我们也可以记 \(\pi(s)=\{\pi(s,0), \pi(s,1), \cdots, \pi(s,|s|-1)\}\)。
举一个实例:\(\pi(\texttt{abacaba})=\{0,0,1,0,1,2,3\}\)。
1.2. 前缀函数的重要性质
显然直接根据定义计算前缀函数的时间复杂度是不能接受的,于是我们需要依赖一些有用的性质来加速计算。
主要的性质有下面两个:
- \(\pi(i+1)\leqslant\pi(i)+1\),且仅当 \(s_{\pi(i)}=s_{i+1}\) 时有 \(\pi(i+1)=\pi(i)+1\)。
- 令 \(j\) 为 \(s[:i]\) 次长的 \(\text{Border}\) 的长度,则 \(j=\pi(\pi(i)-1)\)。
第一个性质是显然的。第二个性质由 \(s[:j-1]=s[i-j+1:i]=s[\pi(i)-j:\pi(i)-1]\) 自然导出。
1.3. 快速求前缀函数
这样我们就有了快速求前缀函数的算法:
我们从前向后迭代求。假设我们已经求出了 \(\pi(0), \pi(1), \cdots, \pi(i-1)\),现在要求 \(\pi(i)\)。
令 \(j=\pi(i-1)\)。如果 \(s_i=s_j\),则 \(\pi(i)=j+1\)。
否则,令 \(j\gets \pi(j-1)\),重复以上判断直到满足 \(s_i=s_j\) 或 \(j=0\)。
若 \(j=0\),则 \(\pi(i)=0\)。
如此重复直到前缀函数全部计算完成。可以证明,时间复杂度为 \(O(n)\)。
code:
void prefix()
{
for(int i=1;i<=n-1;i++)
{
int j=pi[i-1];
while(j>0&&s[i]!=s[j])j=pi[j-1];
if(s[i]==s[j])j++;
pi[i]=j;
}
}
1.4. 前缀函数的应用
1.4.1. KMP
luoguP3375
一般的方法是通过 \(\pi(i)\) 优化匹配,不过有个简单粗暴地多的方法。
假设模式串为 \(s\),文本串为 \(t\),\(s\) 的长度为 \(n\),\(t\) 的长度为 \(m\)。
构造字符串 \(a\gets s+\#+t\),其中 \(\#\) 是 \(s,t\) 中均不会出现的字符。
然后求 \(a\) 的前缀函数,若\(\pi(a,i)=n\),则 \(s\) 在 \(t\) 的 \(i-2n\) 处出现。
总时间复杂度 \(O(n+m)\)。代码略。
优化:注意到前缀函数是可以一个一个字符在线处理的,所以空间复杂度可优化到 \(O(n)\)。
1.4.2 字符串的周期
luoguP4391
对于字符串 \(s\),若 \(\exist p,\ 0<p\leqslant |s|\) 使 \(\forall i\in [0,|s|-p-1],\ s_i=s_{i+p}\),则称 \(p\) 是 \(s\) 的周期。
运用 \(\text{Border}\) 理论,可以发现,若 \(s\) 存在一个长为 \(l\) 的 \(\text{Border}\),则 \(|s|-l\) 是 \(s\) 的周期。
我们应用之前的结果,可知 \(|s|-\pi(|s|-1),\ |s|-\pi(\pi(|s|-1)-1),\cdots\) 为 \(s\) 的周期,其中最小的周期为 \(|s|-\pi(|s|-1)\)。
1.4.3 统计每个前缀的出现次数
根据前缀函数的定义及其性质,以 \(i\) 为右端点有长度为 \(\pi(i),\ \pi(\pi(i)-1),\cdots\) 的前缀。
我们不能按照端点来统计,因为在极端情况下,如 \(\texttt{aaaaaa}\),时间复杂度达到了平方级别。
但是注意到每一个长为 \(i\) 的前缀的出现中也一定包含长为 \(\pi(i-1)\) 的前缀的出现,因此我们可以考虑用较长的前缀的值来更新较短的前缀的值。(具体见代码)
最后别忘了加上每个前缀在初始位置出现的一次。
code:
for(int i=1;i<n;i++)ans[pi[i]]++;
for(int i=n-1;i>0;i--)ans[pi[i-1]]+=ans[i];
for(int i=1;i<=n;i++)ans[i]++;
2. Z函数
2.1. Z函数的定义
定义 \(\text{lcp}(a,b)\) 为字符串 \(a, b\) 的最长公共前缀。
定义Z函数:
对于字符串 \(s\),定义Z函数 \(z(s,i)\) 为 \(\text{lcp}(s,s[i:])\) 的长度。
在不引起歧义的情况下,可简记为 \(z(i)\);特别地,定义 \(z(s,0)=0\)。
根据以上定义,我们有 \(s[:z(i)-1]=s[i:i+z(i)-1]\);对此我们称 \(\text{Z-box}(i)=s[i:i+z(i)-1]\)。
有时我们关心一整个字符串的Z函数值,故此时我们也可以记 \(z(s)=\{z(s,0), z(s,1), \cdots, z(s,|s|-1)\}\)。
举一个实例:\(z(\texttt{abacaba})=\{0,0,1,0,3,0,1\}\)。
2.2. 快速求Z函数
想要快速求出Z函数的值,我们需要充分发掘Z函数的性质以使信息得到高效利用。
考虑若\(l \leqslant i \leqslant l+z(l)-1\),即 \(i\) 在 \(\text{Z-box}(l)\)之内:
方便起见,设 \(r=l+z(l)-1\)。
根据Z函数的定义有 \(s[i-l:r-l]=s[i:r]\),则 \(s[i-l:i-l+z(i)-1]=\text{Z-box}(i)=s[:z(i)-1]\)。
很明显,若 \(z(i-l)<r-i+1\),则直接有 \(z(i)=z(i-l)\);否则,我们能得到 \(z(i) \geqslant r-i+1\),想要得出具体数值还需向后枚举。
为了更好地利用上述性质,我们可以在计算过程中维护最大的 \(r\)。具体操作如下:
初始时令 \(l=r=0\)。
从前到后开始计算。若 \(i\leqslant r\),利用上述性质进行计算。否则,暴力枚举。
如果新的 \(r\) 比之前的更大,即 \(i+z(i)-1>r\),更新 \(l=i,\ r=i+z(i)-1\)。
code:
z[0]=l=r=0;
for(int i=1;i<n;i++)
{
if(r>i)z[i]=min(z[i-l],r-i+1);//简便起见,我们可以直接让z[i]取为z[i-l]和r-i+1中较小的一个
for(;b[i+z[i]]==b[z[i]];z[i]++);//暴力枚举
if(i+z[i]-1>r){l=i;r=i+z[i]-1;}//更新l,r
}
可以看到每个字符只会被暴力匹配一次,因此复杂度为 \(O(n)\)。
这个网站有对该算法的演示。
luoguP5410
对于这道题,我们还要求 \(b\) 与 \(a\) 的每一个后缀的 \(\text{lcp}\)。可以对之前的算法进行修改,也可以直接把 \(a\) 接到 \(b\) 的后面来做。
前缀函数与Z函数介绍的更多相关文章
- linux 文件名称前后缀操作函数----取目录函数dir、取文件名称函数notdir、取后缀函数suffix、取前缀basename、加后缀函数addsuffix、加前缀addprefix、连接函数join
1.1 文件名操作函数 下面我们要介绍的函数主要是处理文件名的.每个函数的参数字符串都会被当做一个或是一系列的文件名来对待. 1.1.1 取目录函数dir $(dir < ...
- ORACLE常用数值函数、转换函数、字符串函数介绍
ORACLE常用数值函数.转换函数.字符串函数介绍. 数值函数: abs(m) m的绝对值 mod(m,n) m被n除后的余数 power(m,n) m的n次方 round(m[,n]) m四舍五入至 ...
- 浅谈 es6 箭头函数, reduce函数介绍
今天来谈一下箭头函数, es6的新特性 首先我们来看下箭头函数长什么样子, let result = (param1, param2) => param1+param2; 上述代码 按照以前书写 ...
- exkmp(Z函数) 笔记
exkmp 用于求解这样的问题: 求文本串 \(T\) 的每一个后缀与模式串 \(M\) 的匹配长度(即最长公共前缀长度).特别的,取 \(M=T\),得到的这个长度被称为 \(Z\) 函数.&quo ...
- Atcoder Regular Contest 058 D - 文字列大好きいろはちゃん / Iroha Loves Strings(单调栈+Z 函数)
洛谷题面传送门 & Atcoder 题面传送门 神仙题. mol 一发现场(bushi)独立切掉此题的 ycx %%%%%%% 首先咱们可以想到一个非常 naive 的 DP,\(dp_{i, ...
- KMP&Z函数详解
KMP 一些简单的定义: 真前缀:不是整个字符串的前缀 真后缀:不是整个字符串的后缀 当然不可能这么简单的,来个重要的定义 前缀函数: 给定一个长度为\(n\)的字符串\(s\),其 \(前缀函数\) ...
- swift1.2语言函数和闭包函数介绍
swift1.2语言函数和闭包函数介绍 在编程中,随着处理问题的越来越复杂,代码量飞速增加.其中,大量的代码往往相互重复或者近似重复.如果不采有效方式加以解决,代码将很难维护. swift1.2语言函 ...
- [SQL SERVER系列]之常用函数和开窗函数介绍及实例
本文主要介绍SQL SERVER数据库中一些常用的系统函数及其SQL SERVER 2005以上支持的开窗函数. 1.常用函数 --从字符串右边截取指定字符数 select RIGHT('HELLO' ...
- NSIS文字及字符串函数与头文件介绍
原文 NSIS文字及字符串函数与头文件介绍 文字函数,顾名思义就是处理字符串的函数.使用这些字符串函数前,必须先包含头文件WordFunc.nsh.该头文件目前包含如下一些函数:WordFind.Wo ...
随机推荐
- 【LeetCode】85. Maximal Rectangle 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址: https://leetcode.com/problems/maximal- ...
- 【LeetCode】526. Beautiful Arrangement 解题报告(Python & C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...
- 【Java笔记】applet和html注意
1.首先记得extends Applet 泪目 2.如果不分包,HTML可以写 <applet code="HelloWorldApplet.class" width=150 ...
- html5调用摄像头并拍照
随着flash被禁用,flash上传附件的方式已成为过去,现在开始用html5上传了.本片文章就是介绍如何使用html5拍照,其实挺简单的原理: 调用摄像头采集视频流,利用canvas的特性生成bas ...
- [opencv]GeneralProcessing_Template_Function
// // Created by leoxae on 2019-05-08. // #ifndef OPENCVDEMO_UTILS_H #define OPENCVDEMO_UTILS_H #inc ...
- 网络划分和各层协议以及webservice 浅谈
最近在公司做一些和其他外部系统接口调用的工作,遇到一些网络传输的问题,趁周末的时间记录.整理一下. 提到网络我们不得不提网络的分层架构: 我们通常听到 网络七层架构/五层架构/四层架构,但是不了解很容 ...
- centos6.5-Apache优化
Apache的网页压缩功能 一.配置网页压缩功能 在配置压缩功能以前访问网页的响应头部 Response Headers view source Accept-Ranges:bytes Connect ...
- 前端如何低门槛开发iOS、Android、小程序多端应用
现如今跨平台开发技术已不是什么新鲜话题了,在市面上也有一些开源的框架可供选择,然而技术成熟.产品服务健全的平台并不多,其中也不乏推陈出新的框架值得关注. 比如最近使用的AVM,由APICloud迭代推 ...
- 『无为则无心』Python函数 — 34、lambda表达式
目录 1.lambda的应用场景 2.lambda语法 3.快速入门 4.示例:计算a + b 5.lambda的参数形式 6.lambda的应用 lambda表达式的主要作用就是化简代码. 匿名函数 ...
- PowerShell【变量篇】
PS C:\Users\Administrator> $str='这是一个变量' PS C:\Users\Administrator> $str 这是一个变量 PS C:\Users\Ad ...