Living-Dream 系列笔记 第74期

news/2025/3/16 15:10:21/文章来源:https://www.cnblogs.com/XOF-0-0/p/18342978

Kobe-Morris-Pratt 算法

定义

一些基本定义:

  • border:是一个字符串的子串(不与其本身相同)且满足既是其前缀又是其后缀的字符串,我们称之为该字符串的一个 border

Kobe-Morris-Pratt 算法(以下简称 KMP 算法),是解决字符串匹配问题的一种算法,实际做题中常偏思维,通常用到的只有其中的 border 相关性质(即通常所说的 \(nxt\) 数组)。

在字符串匹配中,我们通常将被匹配的串称为文本串 \(s\),将与文本串匹配的串称为模式串 \(t\),下文亦同,且我们令 \(\left| s \right|\) 表示字符串 \(s\) 的长度,同时 \(\left| s \right| =n, \left| t \right|=m\)

在字符串匹配中,当 \(s\)\(t\) 某一下标的字符不相同,则我们称此种情况为 失配,下文亦同。

朴素匹配

显然,朴素匹配即为每次失配则令 \(t\) 移动一位继续尝试匹配直到全匹配上为止。

时间复杂度 \(O(n \times m)\)

KMP 匹配

我们观察到,朴素匹配之所以效率低,是因为它每次失配时只让 \(t\) 移动一位。而失配时可能前面已经匹配了许多位,只移动一位就会导致大量的相同字符重复匹配。

结论:KMP 在每次失配时,\(t\) 串下标应跳到从开头到失配前一位的子串的最长 border 的长度的下标处

证明:

  • Q:为什么要跳到 border 的长度下标处?

    A:画个图观察可得,这样跳就是为了使 \(s\)\(t\) 的后缀与前缀对齐。

  • Q:为什么要让后缀与前缀对齐?取中间的不行吗?

    A:因为这样跳的距离更远,效率更高。

  • Q:为什么要跳到最长 border 的长度下标处?

    A:选最长的 border,就意味着在 \(s\) 的最长后缀之前,不会有任何子串与 \(t\) 最长前缀有匹配的可能,那肯定选择直接跳过。而选的更小,则会导致前面可能有匹配的可能,从而导致遗漏。

综上所述即为 KMP 算法的主要逻辑与原理。

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

最长 border 的求取

我们令 \(nxt_i\) 表示 \(t\) 在下标 \(i\) 失配后要跳到的位置。

根据上述推论,它同时也表示 \(t\) 在下标 \(i-1\) 结尾的最长 border 的长度。

既然是求最长相同的前缀与后缀,那么我们也可以不断尝试将 \(t\) 进行自匹配,每次成功匹配就记录 \(nxt\) 值,失配也按上述处理即可。

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

综上,KMP 算法的总时间复杂度即为 \(O(n+m)\)

P3375

板子。

code
#include<bits/stdc++.h>
using namespace std;const int N=1e6+5; 
string s,t;
int nxt[N];void getnxt(){int i=0,j=-1;nxt[0]=-1;for(;i<t.size();){if(j==-1||t[i]==t[j])i++,j++,nxt[i]=j;else j=nxt[j];}
}
void kmp(){getnxt();int i=0,j=0;for(;i<s.size();){if(j==t.size()-1&&s[i]==t[j])cout<<i-j+1<<'\n',j=nxt[j];if(j==-1||s[i]==t[j])i++,j++;elsej=nxt[j];}
}int main(){cin>>s>>t;kmp();for(int i=1;i<=t.size();i++) cout<<nxt[i]<<' ';return 0;
}

P4391

结论:答案即为 \(n-nxt_n\)。(KMP 题中有很多结论题,被薄纱了 /kk)

证明:

  • Case 1:字符串最长 border 无重叠部分。

image

首先蓝色部分一定相等。

由于题目给的是子串,所以我可以复制一个红色部分使得两部分相等。

此时最短的为红 + 蓝,即 \(n-nxt_n\)

还能更短吗?不能,因为空出来的部分没有子串可以复制出它。

  • Case 2:字符串最长 border 有重叠部分。

image

(重叠部分为最中间的蓝色块)

首先,字符串内的蓝 + 红 + 蓝是一个最长 border,它们是相等的。

因为它们相等,蓝色部分又是后缀的一个前缀,所以我在前缀中也能找到一个相同的前缀,同理在后缀中也能找到一个相同后缀,复制一个红色部分后,后缀的相同后缀就能作为一个新的循环节的前缀了。

综上,最短的是蓝 + 红,即 \(n-nxt_n\)

不能更短的原因同上。

code
#include<bits/stdc++.h>
using namespace std;const int N=1e6+5; 
string s;
int n,nxt[N];void getnxt(){int i=0,j=-1;nxt[0]=-1;for(;i<n;){if(j==-1||s[i]==s[j])i++,j++,nxt[i]=j;else j=nxt[j];}
}int main(){cin>>n>>s;getnxt();cout<<n-nxt[n];return 0;
}

CF1200E

进食后人:不要用 substr / +,这俩玩意都是 \(O(n)\) 的,用 erase / += 替代即可。

容易观察到一个很显然的结论:将一个字符串接到之前答案的前面,记所拼成的新字符串的最长 border 的长度为 \(x\),则只要取前一个字符串加上后一个字符串去掉前 \(x\) 位即为当前答案。

然后直接做即可。

注意:

  • 因为最长 border 长度不会超过两字符串长度取 \(\min\),于是只需取之前答案的这么多位即可,不然会超时;

  • 拼接时要在中间加一个任意字符(不是大小写字母或数字),不然答案可能会越界。

code
#include<bits/stdc++.h>
using namespace std;const int N=1e6+5;
int n,nxt[N];
string s[N];int main(){ios::sync_with_stdio(0);cin>>n;string ans="";for(int i=1;i<=n;i++){cin>>s[i];if(i==1) ans+=s[i];else{string now="";now+=s[i];now+="#";now+=ans.substr(ans.size()-min(ans.size(),s[i].size()));int j=0,k=-1;nxt[0]=-1;for(;j<now.size();){if(k==-1||now[j]==now[k])j++,k++,nxt[j]=k;elsek=nxt[k];}s[i].erase(0,nxt[now.size()]);ans+=s[i];}}cout<<ans;return 0;
}

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

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

相关文章

nvm--node【 node.js version management】node.js的版本管理工具

1.卸载node 如果你已经安装了node,那么你需要先卸载node(不然安装nvm可能会失败),如果你没有安装那直接跳过这一步到下一步。 打开控制面板 -> 打开程序和功能 -> 右上角搜索输入node -> 右键卸载 为了确保彻底删除node在看看你的node安装目录中还有没有node文件夹…

windows xusb21.sys驱动对虚拟手柄个数限制

由于windows授权限制,云游戏服务器上的windows版本多数为server 2019 部分游戏用到了手柄,调研后基于 https://github.com/nefarius/ViGEmBus 来魔改虚拟出84个手柄 (一个容器只跑一个游戏,一个游戏独立使用4个手柄,一台云游戏服务器预开21个容器,所以理论至少需要能创建…

ComfyUI插件:ComfyUI-BrushNet节点

前言: 学习ComfyUI是一场持久战,而ComfyUI-BrushNet是最近的局部重绘节点,其包含BrushNet和Powerpaint两个主要节点,其中BrushNet有SD1.5和SDXL两个版本,PowerPaint只有1.5的模型可以使用,学会该插件,你可以完成对图片的局部重绘以及产品换背景等多个工作流。祝大家学习…

[学习笔记]后缀数组(Suffix Array)

后缀数组(suffix array)是一个通过对字符串的所有后缀经过排序后得到的数组。后缀数组被 Manber 和 Myers 于1990年提出, 作为对后缀树的一种替代, 更简单以及节省空间。它们也被Gaston Gonnet 于1987年独立发现, 并命名为“PAT数组”。 后缀数组有很多奇妙的性质, 这些性质可以…

2023 福建省第三届工业互联网创新大赛CTF Misc-Covertchannel2

2023 福建省第三届工业互联网创新大赛CTF Misc-Covertchannel2题目:近日,公司Windows服务器被入侵,黑客使用了一个比较隐蔽的信道将机密凭据传输了出去,但是蛛丝马迹还是被流量采集设备捕获了,你能从中找回丢失的flag吗?分析: 分析该流量包发现了有一个 rsa.key,并且在…

mybatis 源码环境搭建

参考 从头到尾手把手教你搭建阅读Mybatis源码的环境(程序员必备技能) Cannot enable lazy loading because Javassist is not available. Add Javassist to your classpath. 下载源码 https://github.com/mybatis/mybatis-3/ https://github.com/mybatis/parent/tree/mybatis-p…

05、Pod网络

4.1 Pod网络 在K8s集群中,多个节点上的Pod相互通信,要通过网络插件来完成,比如Calico网络插件。 使用kubeadm初始化K8s集群时,有指定一个参数 --pod-network-cidr=10.18.0.0/16 它是用来定义Pod的网段。 而我们在配置Calico的时候,同样也定义了一个CALICO_IPV4POOL_CIDR的…

Ros2 Moveit2 之 围绕对象进行规划 - 添加障碍物

本教程将向您介绍如何将对象插入规划场景并围绕它们进行规划。 先决条件 如果您还没有这样做,请确保您已完成RViz 中的可视化hello_moveit中的步骤。本项目假设您从上一个教程结束的地方开始。如果您只想运行本教程,您可以按照Docker 指南启动一个包含已完成教程的容器。 步骤…

使用 clearError 清除已处理的错误

title: 使用 clearError 清除已处理的错误 date: 2024/8/5 updated: 2024/8/5 author: cmdragon excerpt: 摘要:“文章介绍了clearError函数的作用与用法,用于清除已处理的错误并可实现页面重定向,提升用户体验。通过示例展示了在表单提交场景中如何应用此函数进行错误处理…

破局SAP实施难题、降低开发难度,定制化需求怎样快速上线?

前言 SAP 是全球领先的业务流程管理软件供应商之一,其提供广泛的模块化解决方案和套件,所开发的软件解决方案面向各种规模的企业,帮助客户规划和设计业务流程、分析并高效设计整个价值链,以更好的了解和响应客户需求。ERP 是企业资源规划的简称,ERP 软件涵盖所有核心业务领…

ComplatebleFuture异步调用方法,喝茶你也可以很快

ComplatebleFuture的异步用法: ComplateFuture.supplyAsync()方法会将方法体里面的方法进行异步调用,不用一直等待; ComplateFuture.allof()方法用于等待所有complatebleFutrue方法执行完毕。