前缀函数

我个人觉得 oiwiki 上的学习顺序是很合理的,学 KMP 之前先了解前缀函数是非常便于理解的。

前后缀定义

前缀 prefixprefixprefix 指的是从字符串 SSS 的首位到某个位置 iii 的一个子串,这样的子串写作 prefix(S,i)prefix(S,i)prefix(S,i)

后缀 suffixsuffixsuffix 指的是从字符串 SSS 的某个位置 iii 到末尾的一个子串,这样的子串写作 suffix(S,i)suffix(S,i)suffix(S,i)

SSS真前缀 指的是不等于 SSS 的一个前缀,SSS真后缀 指的是不等于 SSS 的一个后缀。

S=aabS=aabS=aab,那么 aabaabaabSSS 的一个前缀,但不是 SSS 的真前缀,是 SSS 的后缀,但不是 SSS 的真后缀。

前缀函数定义

给定一个长度为 nnn 的字符串 sss,其 前缀函数 写作 π\piπ ,则 π(i)\pi(i)π(i) 的定义为子串 s[0...i]s[0...i]s[0...i]最长相等真前缀与真后缀 长度。

意思就是

  1. 如果子串 s[0...i]s[0...i]s[0...i] 有一对相等的真前缀与真后缀,那么 π(i)\pi(i)π(i) 就是这个相等的真前缀的长度。
  2. 如果有多对相等的,π(i)\pi(i)π(i) 取最长的一对作为答案。
  3. 如果不存在相等,那么 π(i)=0\pi(i)=0π(i)=0

s=aabbas=aabbas=aabba,则 π(0)=0,π(1)=1,π(2)=0,π(3)=0,π(4)=1\pi(0)=0,\pi(1)=1,\pi(2)=0,\pi(3)=0,\pi(4)=1π(0)=0,π(1)=1,π(2)=0,π(3)=0,π(4)=1

其中 π(0)\pi(0)π(0) 表示字符串 aaa 的最长相等真前缀与真后缀,由于 aaa 长度为 111,所以没有真前缀,故 π(0)=0\pi(0)=0π(0)=0

其中 π(4)\pi(4)π(4) 表示字符串 aabbaaabbaaabba 的最长相等真前缀与真后缀,答案是 aaa,故 π(4)=1\pi(4)=1π(4)=1

前缀函数的求法

朴素算法

利用双重循环,第一重循环枚举 当前子串长度 s[0...i]s[0...i]s[0...i],第二层循环枚举子串的所有 真前缀的长度,长度从大到小枚举,并判断当前真前缀与真后缀是否相同,如果相同的话当前长度就等于 π(i)\pi(i)π(i)

for (int i = 1; i < s.size(); i++) {for (int j = i; j >= 0; j--) {if (s.substr(0, j) == s.substr(i - j + 1, j)) {p[i] = j;break;}}
}

其中 s.substr(pos, len) 是字符串的一个函数,意思是提取出 ssspospospos 位置开始往下数 lenlenlen 个字符的子串,等价于子串 s[pos...pos+len−1]s[pos...pos+len-1]s[pos...pos+len1],要减 111 是因为从 pospospos 开始,pospospos 也算一个字符。

所以 s.substr(0, j) 表示的是子串 s[0...j−1]s[0...j-1]s[0...j1]s.substr(i - j + 1, j) 表示的是子串 s[i−j+1,i]s[i-j+1,i]s[ij+1,i]

该算法的时间复杂度是 O(n3)O(n^3)O(n3)

优化一

当我们求 π(i)\pi(i)π(i) 的时候,我们没有 充分运用 之前求过的 π\piπ 值。

对于 s[0...i]s[0...i]s[0...i],考虑如何充分利用 π(i−1)\pi(i-1)π(i1)

  1. π(i−1)=0\pi(i-1)=0π(i1)=0,说明 π(i)\pi(i)π(i) 的值 至多111。如果 π(i)\pi(i)π(i) 的值大于 111,说明 s[0...i−1]s[0...i-1]s[0...i1] 的最长相等真前缀与后缀的长度至少为 111,与 π(i−1)=0\pi(i-1)=0π(i1)=0 矛盾。
  2. π(i−1)≠0\pi(i-1)\neq 0π(i1)=0,如果 s[i]==s[π(i−1)]s[i]==s[\pi(i-1)]s[i]==s[π(i1)],那么 π(i)=π(i−1)+1\pi(i)=\pi(i-1)+1π(i)=π(i1)+1。否则 π(i)\pi(i)π(i) 的大小必然小于等于 π(i−1)\pi(i-1)π(i1)

不难发现,π(i)\pi(i)π(i)上限至多π(i−1)\pi(i-1)π(i1)111,所以第二重循环只需要从 π(i−1)+1\pi(i-1)+1π(i1)+1 枚举即可。

for (int i = 1; i < s.size(); i++) {for (int j = p[i - 1] + 1; j >= 0; j --) {if (s.substr(0, j) == s.substr(i - j + 1, j)) {p[i] = j;break;}}
}

关于时间复杂度的计算,当我们计算 π(i)\pi(i)π(i) 的时候 多枚举xxx 次,说明 π(i)\pi(i)π(i) 的值相对于 π(i−1)\pi(i-1)π(i1) 减少xxx。也就是说 π(i+1)\pi(i+1)π(i+1) 的第二重循环的上限也就 减少xxx

也就是说,多增加的次数,在后续的求解中会被抵消,那么就只剩下了最终至少需要枚举的第一次。

所以第二重循环的时间就主要在 substr 函数的 O(n)O(n)O(n) 上,故总时间复杂度为 O(n2)O(n^2)O(n2)

优化二

第二重循环从 π(i−1)+1\pi(i-1)+1π(i1)+1 开始遍历,每次判定还是依靠了 substr,有没有不用 substr 的方法?

如果想不用 substr 就能判断前缀后缀是否相等,说明我们就得跳到 前缀后缀一定相等 的位置。

也就是说当 s[π(i−1)]≠s[i]s[\pi(i-1)]\neq s[i]s[π(i1)]=s[i] 的时候,我们就得找到一个仅次于 π(i−1)\pi(i-1)π(i1) 的长度 jjj,使得 s[0...j−1]=s[i−j...i−1]s[0...j-1]=s[i-j...i-1]s[0...j1]=s[ij...i1],如果找到了这样的 jjj,我们再判断 s[j]s[j]s[j]s[i]s[i]s[i] 是否相等就行了。

如果相等,说明 π(i)=j\pi(i)=jπ(i)=j,否则我们就找下一个仅次于 jjj 的长度 … 直到 jjj 削减为 000,此时 π(i)=0\pi(i)=0π(i)=0

在这里插入图片描述

我们可以看到这张图,当 s[π(i−1)]s[\pi(i-1)]s[π(i1)]s[i]s[i]s[i] 匹配失败,我们就要找一个仅次于 π(i−1)\pi(i-1)π(i1) 的长度 jjj,使之满足s[0...j−1]=s[i−j...i−1]s[0...j-1]=s[i-j...i-1]s[0...j1]=s[ij...i1],在图上就是深红色的两个位置。

又因为一定有 s[0...p[i−1]−1]=s[i−p[i−1]...i−1]s[0...p[i-1]-1]=s[i-p[i-1]...i-1]s[0...p[i1]1]=s[ip[i1]...i1] 成立,这是 π(i−1)\pi(i-1)π(i1) 的定义,所以可以认为 s[0...p[i−1]−1]s[0...p[i-1]-1]s[0...p[i1]1]后缀必然有一个长度为 jjj 的子串 等于 s[i−j...i−1]s[i-j...i-1]s[ij...i1]

又因为 s[0...p[i−1]−1]s[0...p[i-1]-1]s[0...p[i1]1]前缀必然有一个长度为 jjj 的子串 等于 s[i−j...i−1]s[i-j...i-1]s[ij...i1],所以 s[0...p[i−1]−1]s[0...p[i-1]-1]s[0...p[i1]1]一对相等的前后缀,其长度为 jjj

所以我们可以得出,下一个长度仅次于 π(i−1)\pi(i-1)π(i1) 的长度 jjj 等于 π(π(i−1)−1)\pi(\pi(i-1)-1)π(π(i1)1)

于是,我们就可以省略掉 substrO(n)O(n)O(n),只需要每次去比较 s[j]s[j]s[j]s[i]s[i]s[i] 是否相等即可。

经过两次优化,求前缀函数的算法的时间复杂度为 O(n)O(n)O(n)

void getPrifixFunction () {p[0] = 0;for (int i = 1; i < n; i++) {int j = p[i - 1];while (j && s[j] != s[i]) {j = p[j - 1];}if (s[j] == s[i]) j ++;p[i] = j;}}

这串代码似乎和上面描述的有一些出入,所以这里解释一下每一句话。

首先 getPrifixFunction 是求前缀函数的函数,前缀函数的第一个值 p[0]=0p[0]=0p[0]=0

然后枚举所有长度的子串 s[0...i]s[0...i]s[0...i],最初 jjj 是满足 s[0...j−1]=s[i−j...i−1]s[0...j-1]=s[i-j...i-1]s[0...j1]=s[ij...i1] 的最大长度 π(i−1)\pi(i-1)π(i1)

然后循环判断是否 s[j]==s[i]s[j]==s[i]s[j]==s[i],如果不等于那么就往下跳到下一个长度 j=π(j−1)j=\pi(j-1)j=π(j1)

最后特判一下长度为 111 的情况,因为长度为 111 的时候是 s[0]==s[i]s[0]==s[i]s[0]==s[i],所以 jjj 已经削减到 000 了。

#include <bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define int long long
#define endl '\n'
#define PII pair<int,int>
#define INF 1e18
const int N = 1e6 + 7;struct PrifixFunction {int n;string s;vector <int> p;PrifixFunction (int _n, string _s) : s(_s), n(_n), p(_n + 1){}void getPrifixFunction () {p[0] = 0;for (int i = 1; i < n; i++) {int j = p[i - 1];while (j && s[j] != s[i]) {j = p[j - 1];}if (s[j] == s[i]) j ++;p[i] = j;}}};signed main() {}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/92545.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/92545.shtml
英文地址,请注明出处:http://en.pswp.cn/web/92545.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解决chrome下载crx文件被自动删除,加载未打包的扩展程序时提示“无法安装扩展程序,因为它使用了不受支持的清单版本解决方案”

解决chrome下载crx文件被自动删除 【chrome设置-隐私与安全-安全浏览】&#xff0c;选择 不保护 【chrome设置-下载内容】&#xff0c;勾选 下载前询问每个文件的保存位置 下载crx文件时&#xff0c;选择保存文件夹&#xff0c;将 .crx后缀 改为 .zip后缀&#xff0c;再确定。 …

嵌入式学习day23-shell命令

linux软件编程学习大纲&#xff1a;1.IO操作文件2.多任务编程3.网络编程4.数据库编程5.硬件设备管理学习目标&#xff1a;1.学习接口调用&#xff08;第一层&#xff09;2.软件操作流程和思想&#xff08;第二层&#xff09;3.软件设计思想和流程架构&#xff08;第三层&#x…

GPT-5 系列深度详解:第1章-引言(目录)

1 引言2 模型数据与训练3 观察到的安全挑战与评估 3.1 从强制拒绝到安全完成 3.2 禁⽌内容 3.3 拍⻢屁 3.4 越狱 3.5 指令层级 3.6 幻觉 3.7 欺骗 3.7.1 欺骗思维链监控 3.8 图像输入 3.9 健康 3.10 多语言性能 3.1.1公平性与偏见&#xff1a; BBQ评估4 红队测试与外部评估…

NineData 新增支持 AWS ElastiCache 复制链路

2025 年&#xff0c;绝大多数企业已完成业务上云&#xff0c;以获取更高的弹性、可扩展性和成本效益。AWS ElastiCache 作为 AWS 提供的全托管式内存数据库服务&#xff0c;已成为许多企业在云上构建高并发、低延迟应用的理想选择。NineData 数据复制现已全面支持从自建 Redis …

人工智能-python-特征选择-皮尔逊相关系数

以下是关于特征选择中常用方法的表格总结&#xff0c;并且详细阐述了皮尔逊相关系数的原理、计算方法、步骤以及示例。 常用特征选择方法总结方法原理优点缺点使用场景过滤法&#xff08;Filter Method&#xff09;基于特征的统计信息&#xff08;如相关性、方差等&#xff09;…

LabVIEW多循环架构

​LabVIEW的多循环架构是一种常见的架构&#xff0c;本文Temperature Monitoring.vi 采用 LabVIEW 典型的多循环并行架构&#xff0c;通过功能模块化设计实现温度监测全流程&#xff0c;各循环独立运行又协同工作&#xff0c;构成完整的监测系统。1. 事件处理循环&#xff08;E…

深入理解Maven BOM

一、什么是Maven BOM&#xff1f; 1.1 BOM的基本概念 Maven BOM&#xff08;Bill of Materials&#xff0c;材料清单&#xff09;是一种特殊的POM文件&#xff0c;它主要用于集中管理多个相关依赖的版本。BOM本身不包含任何实际代码&#xff0c;而是作为一个 版本管理的"参…

Mysql分页:高效处理海量数据的核心技术

Mysql分页&#xff1a;高效处理海量数据的核心技术01 引言 在Web应用、移动应用或数据分析场景中&#xff0c;数据库常常需要处理百万甚至千万级的数据记录。一次性加载所有数据不仅效率低下&#xff0c;还会消耗大量网络带宽和内存资源。数据库分页技术正是解决这一挑战的关键…

通过 Docker 运行 Prometheus 入门

Promethues 组件 prometheus serverexporteralertmanager 环境准备 Docker 拉取镜像备用 # https://hub.docker.com/r/prom/prometheus docker pull m.daocloud.io/docker.io/prom/prometheus:main# https://hub.docker.com/r/prom/node-exporter docker pull m.daocloud.io/do…

Java 8特性(一)

目录 一、Lambda表达式 1、语法格式&#xff1a; &#xff08;1&#xff09;接口名 对象名(参数类型1参数名1,....参数类型n 参数名n)->{方法体;} &#xff08;2&#xff09;参数类型h 参数名n:接口中抽象方法的参数项 &#xff08;3&#xff09;->:表示连接操作 &a…

【代码随想录|232.用栈实现队列、225.用队列实现栈、20.有效的括号、1047.删除字符串中的所有相邻重复项】

232.用栈实现队列 timutimtit232. 用栈实现队列 - 力扣&#xff08;LeetCode&#xff09; class MyQueue { public:stack<int> Sin;stack<int> Sout;MyQueue() {}void push(int x) {Sin.push(x);}int pop() {if (Sout.empty()) { // 出栈为空就把入栈的数导出来w…

码上爬第三题【协程+浏览器调试检测】

前言&#xff1a;图灵第三题就是对用户浏览器调试检测&#xff0c;检测鼠标右击打开控制台&#xff0c;检测键盘按键ctrlshifti&#xff0c;从浏览器设置打开开发者工具也不行&#xff0c;应该是有浏览器宽高检测的&#xff0c;所以我们保证浏览器页面宽高不变即可。你如果想右…

windows、linux应急响应入侵排查

windows入侵排查 1.1检查账号 1.查看服务器是否有弱口令&#xff0c;远程管理端口是否对公网开放 2.查看服务器是否存在可疑账号、新增账号 检查方法&#xff1a;打开 cmd 窗口&#xff0c;输入 lusrmgr.msc 命令&#xff0c;查看是否有新增/可疑的账号&#xff0c;如有管…

11. 为什么要用static关键字

11. 为什么要用static关键字 static&#xff1a;通常来说&#xff1a;在new一个对象的时候&#xff0c;数据存储空间才会被分配&#xff0c;方法才能被外界使用。但是有时只想单独分配一个存储空间&#xff0c;不考虑需要创建对象或不创建对象&#xff0c;在没有对象的情况下也…

[Oracle] MAX()和MIN()函数

MAX() 和 MIN() 是 Oracle 常用的聚合函数&#xff0c;用于从一组值中找出最大值和最小值1.MAX()函数MAX()函数返回指定列或表达式中的最大值语法格式MAX(expression)参数说明expression&#xff1a;可以是列名、计算列或表达式示例-- 返回employees表中salary列的最大值 SELEC…

网络资源模板--基于Android Studio 实现的麻雀笔记App

目录 一、测试环境说明 二、项目简介 三、项目演示 四、部设计详情&#xff08;部分) 添加页面 五、项目源码 一、测试环境说明 电脑环境 Windows 11 编写语言 JAVA 开发软件 Android Studio (2020) 开发软件只要大于等于测试版本即可(近几年官网直接下载也可以)&…

96-基于Flask的酷狗音乐数据可视化分析系统

基于Flask的酷狗音乐数据可视化分析系统 &#x1f4cb; 目录 项目概述技术栈系统架构功能特性数据库设计核心代码实现数据可视化部署指南项目总结 &#x1f3af; 项目概述 本项目是一个基于Flask框架开发的酷狗音乐数据可视化分析系统&#xff0c;旨在为用户提供音乐数据的…

Java基础-红包雨游戏-多线程

目录 案例要求&#xff1a; 实现思路&#xff1a; 代码&#xff1a; Employee RedPacket RedPacketRain 总结&#xff1a; 案例要求&#xff1a; 实现思路&#xff1a; 创建一个员工类,id和抢到的金额&#xff0c;创建一个红包类&#xff0c;里面就是金额&#xff0c;创…

[激光原理与应用-203]:光学器件 - 增益晶体 - 增益晶体的使用方法

增益晶体是激光器的核心元件&#xff0c;其作用是通过受激辐射放大光信号。正确使用增益晶体需综合考虑晶体选型、光路设计、热管理、泵浦方式及安全防护等关键环节。以下是增益晶体的详细使用方法及注意事项&#xff1a;一、晶体选型&#xff1a;根据需求匹配参数材料选择Nd:Y…

​什么是抽象主义人工智能?​

什么是抽象主义人工智能&#xff1f; 传统的人工智能分为符号主义和连接主义两个派别&#xff0c;后来又增加了行为主义。 我发现符号主义和连接主义处理的都是文本&#xff0c;而不是语义。原来的专家系统是符号主义的产物。现在的大语言模型是连接主义的产物。它们处理的都…