一种空间复杂度为的线性字符串匹配算法
记号
记为空字符串序列
记为所有字符串序列构成的集合
对于,记为其中字符串个数
对于,记
皮配函数
定义辅助函数,对于
-
若或,则
-
否则,取最小的满足,取满足
设依次为,则
性质1:
(参考中的定义)
当或时显然成立
否则,若,则,矛盾
换言之,有,进而
化简得,显然,即
定义皮配集合
对于,记
定义皮配函数,对于
- 若,则
- 否则,若,则
- 否则,(参考中的定义)
性质2:
- 当或中字符串均相同时显然成立
- 否则,对从小到大归纳,当时显然成立
- 否则,根据归纳假设及性质1,有
增量法
已知,求
-
若,则
-
否则,设,其中
若中字符串均相同,则
-
否则,若不为的后缀,则
-
否则,
后者即"已知,求"的子问题,可以递归处理
记为上述过程的递归次数(包括初始加入)
定义势能函数,其中
性质3:
(参考增量法过程中的定义)
对从小到大归纳,当不进行递归时显然成立
否则,记,则,根据归纳假设
换言之,对于,增量法求的总递归次数即
性质4:
- 对从小到大归纳,当时显然成立
- 否则,根据归纳假设及性质1,有
综上,增量法求的总递归次数(均摊)为
皮配结论
定义上的皮配关系,对于
-
若,则
-
否则,若,则
-
否则,设
若中字符串均相同,则为的后缀
-
否则,且
皮配结论:若,则为的后缀
记
当或时显然成立
否则,有且为的后缀
当中字符串均相同时,有,进而显然成立
否则,对从小到大归纳
若为的后缀,则,即出现位置相同
换言之,需且为的后缀,由归纳假设后者即
另一方面,若满足上述条件,显然为的后缀
字符串匹配
将给定字符串转换为字符串序列(每个元素长度为),设为
根据结论,用增量法求出和,并根据的定义判定即可
在实现中,需实现增量法的每层递归,具体细节如下——
-
字符串以哈希的形式存储,即可拼接和判断是否相同
-
对于,设
满足,仅需维护和即可
需判定是否为的后缀,并求(若成立)
维护末尾的个数(对取)和,判定即且
另外,为了避免减法,可以额外维护
-
由于固定,的判定可以转换为若干个条件,在增量时维护满足的条件数即可
综上,时间复杂度为,空间复杂度为
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=30,B=47,mod=998244353;
int n,m,t,cnt,ans;
struct String{
int s,H;
String(){
s=1,H=0;
}
String(int c){
s=B,H=c;
}
String(int _s,int _H){
s=_s,H=_H;
}
bool operator == (const String &n)const{
return (s==n.s)&&(H==n.H);
}
String operator + (const String &n)const{
return String((ll)s*n.s%mod,(H+(ll)s*n.H)%mod);
}
};
struct Seqx{
bool flag;int c;String a,b;
Seqx(){}
Seqx(String &s){
flag=0,c=1,a=s,b=String();
}
void add(String &s){
if (a==s)c++;
else flag=1,b=s;
}
}X[N];
struct Seqy{
int c;String a,b,a0;
Seqy(){}
Seqy(String &s){
c=1,a=s,b=a0=String();
}
bool add(String &s,Seqx &x){
a=a+s;
if (!x.flag){
if (s==x.a)c++;
else c=0,b=a0=a;
}
else{
if (s==x.a){
if (c<x.c)c++;
else b=b+s;
}
else{
if ((s==x.b)&&(c==x.c))return 1;
c=0,b=a;
}
}
return 0;
}
}Y[N];
struct Data{
Seqx x;Seqy y;Data *z;
}*Ps,*Pt;
char Getchar(){
char c=getchar();
while ((c<'a')||(c>'z'))c=getchar();
return c;
}
bool check(int d,Seqy &y){
return (X[d].flag ? Y[d].a==y.a : X[d].c<=y.c);
}
void add(Data **P,String s,int d,int p){
if ((p)&&(d>=t))return;
if ((p)&&(!check(d,(*P)->y)))cnt--;
if ((*P)==NULL)(*P)=new Data{Seqx(s),Seqy(s),NULL};
else{
if ((*P)->y.add(s,(*P)->x)){
add(&(*P)->z,(*P)->y.b,d+1,p);
(*P)->y.c=0,(*P)->y.a=(*P)->y.b=(*P)->y.a0;
}
if (!(*P)->x.flag)(*P)->x.add(s);
}
if ((p)&&(!check(d,(*P)->y)))cnt++;
}
void get(Data *P){
if (P==NULL)return;
X[t]=P->x,Y[t++]=P->y,get(P->z);
}
void get(Data **P,int d){
if (d==t)return;
(*P)=new Data{X[d],Y[d],NULL},get(&(*P)->z,d+1);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
int c=Getchar()-'a'+1;
add(&Ps,String(B,c),0,0);
}
t=cnt=0,get(Ps),get(&Pt,0);
for(int i=1;i<=m;i++){
int c=Getchar()-'a'+1;
add(&Pt,String(B,c),0,1);
if ((i>=n)&&(!cnt))ans++;
}
printf("%d\n",ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现