KMP1(字符串基本概念,KMP算法和简单应用)
KMP1(字符串基本概念,KMP算法和简单应用)
基础定义
字符串
:无特殊说明,字符串仅由26个小写字母 构成, 并用大写字母表示一个字符串。
:表示一个字符串的长度
: 表示字符串 第 个位置的字母,下标从 开始。
子串
: 表示字符串 从第 到第 个字母顺次连接而成的新字符串。
: 表示字符串 的长度为 的前缀,
:表示字符串 的长度为 的后缀,
Border
如果字符串
的同长度的前缀和后缀完全相同,则称此前缀为一个Border。 特殊地,字符串本身也可以是它的 Border。(根据语境判断)
e.g. 若
周期和循环节
对于字符串
和正整数 ,如果有 , 对于 成立, 则称 为字符串 的一个周期。 特殊地,
一定是 的周期
重要性质
因此周期和Border 等价,遇到题目可以相互转化
注意: Border 不具有二分性!
Border的性质
传递性:
求 S 的所有 Border 等价于求所有前缀的最大Border
KMP
Next数组:
next[i] 表示 pre[i] 的非平凡最大Border
next[1]=0
我们发现
因此,求
即
参考代码:
struct KMP{
vector<int> nxt;
void init(string s){//用来维护出nxt数组
int n=s.size();
nxt.assign(n+1,0);
s="?"+s;
for(int i=2;i<=n;i++){
nxt[i]=nxt[i-1];
while(nxt[i]&&s[nxt[i]+1]!=s[i]) nxt[i]=nxt[nxt[i]];
nxt[i]+=(s[nxt[i]+1]==s[i]);
}
}
};
例题1 字符串的问题
题意:
找出字符串
1.该子串是
2.该子串是
3.该子串除了开头的结尾还出现一次
思路:
转化一下题意,找到最长的在中间出现一次的Border。
朴素做法自然是枚举所有的Border,然后判断是否出现即可。
但其实,我们只用计算
说明对应的Border至少出现了四次。一定是满足题意的。
代码:
struct KMP{
vector<int> nxt;
void init(string s){
int n=s.size();
nxt.assign(n+1,0);
s="?"+s;
for(int i=2;i<=n;i++){
nxt[i]=nxt[i-1];
while(nxt[i]&&s[nxt[i]+1]!=s[i]) nxt[i]=nxt[nxt[i]];
nxt[i]+=(s[nxt[i]+1]==s[i]);
}
}
};
void Showball(){
string s;
cin>>s;
int n=s.size();
KMP kmp;
kmp.init(s);
int border=kmp.nxt[n];
for(int i=1;i<n;i++){
if(border&&kmp.nxt[i]==border){
return cout<<s.substr(0,border),void();
}
}
border=kmp.nxt[kmp.nxt[n]];
if(border==0) cout<<"Just a legend";
else cout<<s.substr(0,border);
}
例题2 字符串匹配(模板)
题意:
给出两个字符串
思路:
枚举每个位置,遇到不同时,只需要跳到下一个Border即可。
template<class Type>
struct KMP{
vector<int> init(Type s){
int n=(int)s.size()-1;
vector<int> nxt(n+1,0);
for(int i=2;i<=n;i++){
nxt[i]=nxt[i-1];
while(nxt[i]&&s[nxt[i]+1]!=s[i]) nxt[i]=nxt[nxt[i]];
nxt[i]+=(s[nxt[i]+1]==s[i]);
}
return nxt;
}
vector<int> match(Type s,Type t){
vector<int> pos;
vector<int> nxt=init(t);
int n=(int)s.size()-1,m=(int)t.size()-1;
for(int i=1,j=0;i<=n;i++){
while(j&&s[i]!=t[j+1]) j=nxt[j];
j+=(t[j+1]==s[i]);
if(j==m){//匹配成功
j=nxt[j];
pos.push_back(i-m+1);
}
}
return pos;
}
};
KMP<string> kmp;
void Showball(){
string s,t;
cin>>s>>t;
s="?"+s;
t="?"+t;
vector<int> nxt=kmp.init(t);
vector<int> pos=kmp.match(s,t);
for(auto x:pos) cout<<x<<endl;
int n=t.size();
for(int i=1;i<n;i++) cout<<nxt[i]<<" \n"[i==n-1];
}
例题3 栗酱的数列
题意:
给你一个长度为
求出
思路:
遇到这种式子,一个很经典的处理方式就是将相同类型的移项到同一边。于是有
代码:
void Showball(){
int n,m,k;
cin>>n>>m>>k;
vector<int> a(n+1),b(m+1);
for(int i=1;i<=n;i++){
cin>>a[i];
a[i]%=k;
}
for(int i=1;i<=m;i++){
cin>>b[i];
b[i]%=k;
}
vector<int> da(n),db(m);
for(int i=1;i<n;i++) da[i]=(a[i]-a[i+1]+k)%k;
for(int i=1;i<m;i++) db[i]=(k-(b[i]-b[i+1]+k)%k)%k;
vector<int> ans=kmp.match(da,db);
cout<<ans.size()<<endl;
}
拓展
Border的性质:
周期定理:若
均为串 的周期,则 也为 的周期 一个串的 Border 数量是
个,但他们组成了 个等差数列
KMP 的推广
扩展KMP(Z 算法)
KMP 自动机, Border树
AC自动机, 即KMP的多串模式。
Trie图, 即KMP自动机的多串模式。
Border树
定义:
对于一个字符串
, , 它的 树 共有 个节点: 。
是这课有向树的根,对于其他点,每个点的父节点是
性质:
1.每个前缀
的所有Border:节点 到根的链 2.哪些前缀有长度为
的Border: 的子树 3.求两个前缀的公共Border 等价于求LCA
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!