首页 > 编程语言 >KMP1(字符串基本概念,KMP算法和简单应用)

KMP1(字符串基本概念,KMP算法和简单应用)

时间:2024-07-29 20:18:52浏览次数:9  
标签:nxt int KMP1 vector KMP 字符串 Border 基本概念

KMP1(字符串基本概念,KMP算法和简单应用)

基础定义

字符串

\(S\):无特殊说明,字符串仅由26个小写字母\('a'-'z'\) 构成, 并用大写字母表示一个字符串。

\(|S|\):表示一个字符串的长度

\(S[i]\) : 表示字符串 \(S\) 第 \(i\) 个位置的字母,下标从 \(1\) 开始。

子串

\(S[l,r]\) : 表示字符串 \(S\) 从第 \(l\) 到第 \(r\) 个字母顺次连接而成的新字符串。

\(Pre[i]\): 表示字符串 \(S\) 的长度为 \(i\) 的前缀, \(Pre[i] = s[1,i]\)

\(Suf[i]\):表示字符串 \(S\) 的长度为 \(i\) 的后缀, \(Suf[i] = s[|S|- i + 1, |S|]\)

Border

如果字符串 \(S\) 的同长度的前缀和后缀完全相同,则称此前缀为一个Border。

特殊地,字符串本身也可以是它的 Border。(根据语境判断)

e.g. 若 \(S = bbabbab\),试求所有 Border

\(b\)

\(bbab\)

周期和循环节

对于字符串 \(S\) 和正整数 \(p\),如果有 \(S[i] = S[i - p]\) , 对于 \(p < i \le |S|\) 成立, 则称 \(p\) 为字符串 \(S\) 的一个周期。

特殊地, \(p = |S|\) 一定是 \(S\) 的周期

重要性质

\(p\) 是 \(S\) 的周期 $\iff |S| - p $ 是 \(S\) 的 Border

因此周期和Border 等价,遇到题目可以相互转化

注意: Border 不具有二分性!

Border的性质

传递性:\(S\) 的 Border 的 Border 也是 \(S\) 的 Border

求 S 的所有 Border 等价于求所有前缀的最大Border

KMP

Next数组

next[i] 表示 pre[i] 的非平凡最大Border

next[1]=0

我们发现 \(pre[i]\) 的 Border 去掉最后一个字母就会变成 \(pre[i-1]\) 的Border

因此,求 \(pre[i]\) 的Border时,可以遍历 \(pre[i-1]\) 的所有Border,

即 \(next[i-1],next[next[i-1]],...,0\) ,检查后一个字母是否等于 \(s[i]\)

参考代码:

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 字符串的问题

题目链接

题意

找出字符串 \(S\) 中满足下列条件的最长子串:

1.该子串是 \(S\) 的前缀

2.该子串是 \(S\) 的后缀

3.该子串除了开头的结尾还出现一次

思路

转化一下题意,找到最长的在中间出现一次的Border。

朴素做法自然是枚举所有的Border,然后判断是否出现即可。

但其实,我们只用计算 \(nxt[n]\) 和 \(nxt[nxt[n]]\)。因为 \(nxt[nxt[n]]\)

说明对应的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 字符串匹配(模板)

题目链接

题意

给出两个字符串 \(S\) 和 \(T\), 求出 \(T\) 在 \(S\) 中所有出现的位置。

思路

枚举每个位置,遇到不同时,只需要跳到下一个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 栗酱的数列

题目链接

题意

给你一个长度为 \(n\) 的数列 \(A\) 和一个长度为 \(m\) 的数列 \(B\)。\((m\le n)\)

求出 \(A\) 中有多少个长度为 \(m\) 的连续子序列 \(A'\) 满足 \((A'_1+B_1)\%k=(A'_2+B_2)\%k=...=(A'_m+B_m)\%k\)。

思路

遇到这种式子,一个很经典的处理方式就是将相同类型的移项到同一边。于是有 \((A'_1-A'_2)\%k=-(B_1-B_2)\%k\)。从 \(1\) 到 \(m-1\) 都满足。那么,我们可以预处理这两个差分数组。然后问题就变成了匹配问题。我们就可以魔改一下 \(KMP\) s算法即可解决。

代码

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的性质

周期定理:若 \(p,q\) 均为串 \(S\) 的周期,则 \(gcd(p,q)\) 也为 \(S\) 的周期

一个串的 Border 数量是 \(O(n)\) 个,但他们组成了 \(O(logN)\) 个等差数列

KMP 的推广

扩展KMP(Z 算法)

KMP 自动机, Border树

AC自动机, 即KMP的多串模式。

Trie图, 即KMP自动机的多串模式。

Border树

定义

对于一个字符串 \(S\), \(n = |S|\) , 它的 \(Border\) 树 共有 \(n+1\) 个节点: \(0,1,2,3,...,n\)。

\(0\) 是这课有向树的根,对于其他点,每个点的父节点是 \(nxt[i]\)

性质

1.每个前缀 \(pre[i]\) 的所有Border:节点 \(i\) 到根的链

2.哪些前缀有长度为 \(x\) 的Border: \(x\) 的子树

3.求两个前缀的公共Border 等价于求LCA

标签:nxt,int,KMP1,vector,KMP,字符串,Border,基本概念
From: https://www.cnblogs.com/showball/p/18330959

相关文章

  • KMP
    基础下文的字符串下标皆从\(1\)开始。考虑定义一个数组\(ne_i\),指的是设字符串\(t\)的前\(i\)位为\(s\)。字符串\(s\)的前\(ne_i\)位与后\(ne_i\)位完全相同,且\(ne_i\)取到了最大值,并且\(ne_i\)不为字符串\(s\)的长度。是不是觉得很绕?我们举一个例子来更好......
  • GRPC: 理解Protocol Buffers和gRPC的基本概念和使用方法
    什么是ProtocolBuffers?ProtocolBuffers(简称protobuf)是由Google开发的一种灵活、高效的结构化数据序列化方法。它类似于XML或JSON,但具备更小、更快、更简单的特点。protobuf主要用于定义数据的结构,然后生成用于解析和序列化数据的代码。这些代码可以用于各种编程语言,如Jav......
  • 【Linux应用编程】Day10_进程 一文详细剖析进程,从基本概念到创建再到进程操作直至消亡
    进程详细剖析进程,包括以下内容:⚫程序与进程基本概念;⚫程序的开始与结束;⚫进程的环境变量与虚拟地址空间;⚫进程ID;⚫fork()创建子进程;⚫进程的消亡与诞生;⚫僵尸进程与孤儿进程;⚫父进程监视子进程;⚫进程关系与进程的六种状态;⚫守护进程;⚫进程间通信概......
  • WLAN概述和基本概念
    1、WALN即WirelessLAN(无线局域网),是指通过无线技术构建的无线局域网络。WLAN广义上是指以无线电波、激光、红外线等无线信号来代替有线局域网中的部分或全部传输介质所构成的网络。WLAN是一种基于IEEE802.11标准的无线局域网技术。802.11标准聚焦在TCP/IP对等模型的下两层:......
  • 2.1.1 通信基础的基本概念
    一、信源、信宿、信道、信号信源:信号的来源,数据的发送方。信宿:信号的“归宿”,数据的接收方。信道:信号的通道。一条物理线路通常包括两条信道,即为发送信道和接收信道。信号:数据的载体,信号又可以分为数字信号和模拟信号,数字信号的信号值是离散的,模拟信号的信号值是连续的。......
  • KMP算法中next数组以及nextval的求解(简单,通俗易懂版)
    以一个题为例:计算上图中next[j]以及nextval[j]的值。【本文中j的下标从1开始。】最长公共前后缀:···前缀:不包含最后一个字符的所有以第一个字符开头的连续子串。···后缀:不包含第一个字符的所有以最后一个字符结尾的连续子串。先看next[j],(1)j的下标......
  • KMP算法(简单易懂版)
    首先举个例子,第一步:此时,B与A的值不匹配。第二步:红色箭头左边的主串与模式串的元素是完全匹配的。第三步:模式串中有“AB”子串是相同的。第四步:直接移动模式串,使前缀移动到后缀的位置。最长公共前后缀:···前缀:不包含最后一个字符的所有以第一个字符开头的连续......
  • 数组基本概念
    1.什么是数组2.一维数组的创建和初始化3.一维数组的使用4.⼀维数组在内存中的存储5.sizeof计算数组元素个数6.⼆维数组的创建7.⼆维数组的初始化8.⼆维数组的使⽤9.⼆维数组在内存中的存储1.什么是数组数组是⼀组相同类型元素的集合1.数组中存放的是1个或者多个......
  • 基本概念
    基本概念全文绝大多数内容是对[0]中讲述的粗略抄写和胡乱加工1.整除整除记号\(\mid\)\mid不整除记号\(\nmid\)\nmid概念略2.整值函数就是小奥里的高斯记号,以及取整符号底\(\lfloor~\rfloor\),\(\lfloorx\rfloor=k_{\max},k\lex,k\in\mathb......
  • kmp & fail树 & border
    kmp经典字符串匹配算法。其实是通过找本身前缀\(i\)的最长border,来实现快速匹配的。这里给出border的定义:字符串\(s\)的一个子串既是它的前缀又是它的后缀,则这个子串是它的border(一般不包含本身)kmp通过fail在border上跳,使得每次前面部分的字符串都是相同的,判断新加入的是否匹......