CF1721E 题解(KMP学习笔记)

CF1721E 题解

题面

原题传送门

前置知识

KMP,基本的字符串函数。

(不懂 KMP 的可以翻到最底下看看后记)

思路

对于每个询问,其实只要把 t 接在 s 的后面跑 KMP 即可。(不懂 KMP 的看后记!)

但这样会 TLE。。。

很遗憾,那就多设一个数组 last

lasti,j 表示第 i 个前缀的所有 border(最长公共前后缀)中,最长的使得下一位刚刚好是 j 的位置。

如:若 si+1=j,则 lasti,j=i,否则 lasti,j=lastfi,j

好了,不多说了,上代码!

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int N=1000025;
char s[N];
int n,m,q,last[N][35],f[N];
void init(){
	int j=0; 
    for(int i=2; i<=n; i++){
        while(j&&s[j+1]!=s[i]) j=f[j];
        f[i]=j+=s[j+1]==s[i];
    }
    for(int i=0; i<n; i++){
        for(int j=0; j<26; j++) last[i][j]=last[f[i]][j]; 
		last[i][s[i+1]-'a']=i;
    }
}
int main(){
    scanf("%s",s+1);//从1开始输入 
	n=strlen(s+1);//求原字符串的长度 
	init();//初始化 
	scanf("%d",&q); 
    while(q--){
        scanf("%s",s+n+1);//从n+1开始输入,直接接在原字符串后面 
		m=strlen(s+n+1);//求新输入的字符串的长度 
        for(int i=n+1; i<=n+m; i++){
            f[i]=0; 
			for(int j=0; j<26; j++) last[i][j]=0;
        }
        for(int j=0; j<26; j++) last[n][j]=last[f[n]][j]; 
		last[n][s[n+1]-'a']=n;
		int j=f[n];
        for(int i=n+1; i<=n+m; i++){
            if(s[j+1]!=s[i]) j=last[j][s[i]-'a'];
        	f[i]=j+=s[j+1]==s[i];
            printf("%d ",f[i]);
            for(int k=0; k<26; k++) last[i][k]=last[j][k]; 
			if(i!=n+m) last[i][s[i+1]-'a']=i;
        }
        printf("\n");//别忘了换行 
    }
    return 0;
}

后记

KMP

问题引入

P3375 【模板】KMP

算法简介

KMP 算法(Knuth-Morris-Pratt 算法)是一个字符串匹配算法。

即对于两个字符串 st,长度分别为 nm。我们分别称它为文本串 s 和模式串 t

KMP 可以在 O(n+m) 求得模式串 t 在文本串 s 中的所有出现位置和出现次数。

这里 ts 中出现即为 t 作为 s 的子串。

在 KMP 算法中,我们需要对于模式串先预处理出一个 fail 数组,记为 f 数组。

f[i] 表示 t 的前缀 t0,t1,,ti 的 border 长度。

border 的中文意思为“最长公共前后缀”。对于某个字符串,他的 border 定义为该串最长的不为自身的前缀,使得该串存在一个后缀与这个前缀相同。

算法流程

朴素的暴力方法是枚举 s 的每个长为 m 的子串,暴力判断是否相同,时间复杂度 O(nm)

暴力复杂度不太能接受。

也许在学习了哈希后你会想,上述过程是否可以用哈希优化呢?

但哈希在代码实现上比 KMP 更为复杂,以及常数较大、正确性上的问题也很难使得我们可以“舒服得”使用哈希代替 KMP。

而且 KMP 的核心思想与部分字符串问题的核心思想类似,即在失配的
时候,运用已经配对成功的前缀这个信息。

在匹配过程中,我们一般用两个双指针 i,j,分别指向 s 串和 t 串。

假设我们在 s[i]t[j] 处发生失配,则:

  • j==0,则 i++
  • j>0,则 j=f[j1]

若配对成功,我们可以当作 s[i]t[m] 发生失配,j=f[m1]mt 的长度)。

上述过程中,每次配对成功 i,j 都会加 1(最多 n 次)。

失配时,若 j==0,则 i 会加 1(最多 n 次)。

j>0,则 j 至少减少 1,显然也不超过增加时的 n 次。也就是 st 串匹配的过程,时间复杂度为 O(n) 的。

fail 数组的部分与匹配的部分类似。

我们发现,前缀 t0,t1,,ti 的 border 去掉末尾的字符后,一定是前缀 t0,t1,,ti1 的公共前后缀。

因此,我们考虑从前往后递推计算 fail 数组。

设当前需要计算 f[i],我们按长度从大到小遍历前缀 t0,t1,,ti1 的所有公共前后缀。

若均未找到或找到某个公共前后缀,使得其后面加一个字符 ti 后能成为 t0,t1,,ti 的公共前后缀,即为所求 border。

posted @   naroto2022  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示
花开如火,也如寂寞。