$KMP$学习记
《不浪漫罪名》——王杰
没有花
这刹那被破坏吗
无野火都会温暖吗
无烟花一起庆祝好吗
若爱恋
仿似戏剧那样假
如布景一切都美化
连相拥都参照主角吗
你说我未能定时
令你每天欢笑一次
我没说出一句美丽台词
是你心中一种缺陷定义
流进了眼角里的刺
为何不浪漫亦是罪名
为何不轰烈是件坏事情
从来未察觉我每个动作
没有声都有爱你的铁证
为何苦不浪漫亦是罪名
为何总等待着特别事情
从来未察觉我语气动听
在我呼吸声早已说明
什么都会用一生保证
KMP学习记
时更。
关于前缀
一个在\(kmp\,\)中较重要的思想。
定义:
一个长度为\(n\,\)的字符串,它的前缀为$$s_1,s_1s_2,···,s_1s_2 ······s_n $$
后缀同理。
真前/后缀即去掉原字符串其他的前缀。
对于一个字符串,它的前缀函数\(\pi [i]\)为\(s_1···s_i\,\)中最长的相等的真前缀和真后缀的长度。
例abcabd(下标从1开始)
\(\pi[1]=0\);
\(\pi[2]=0\);
\(\pi[3]=0\);
\(\pi[4]=1\,\),a与a;
\(\pi[5]=2\,\),ab与ab;
\(\pi[6]=0\)。
求解:
暴力做法,复杂度\(O(n^3)\):
for(int i=2;i<=len;i++)
{
for(int j=i;j>=1;j--)
{
if(s.substr(0,j)==s.substr(i-j+1,j))
{
pi[i]=j;
break;
}
}
}
例题
输出该串的所有\(\pi\)函数。
一切仍如昨日所见
#include <bits/stdc++.h>
const int Ratio=0;
const int N=400005;
int kmp[N],ans[N],cnt,len;
char a[N];
namespace Acheron
{
void Awork()
{
while(std::cin>>a)
{
len=strlen(a);cnt=0;
for(int i=1;i<len;i++)
{
int j=kmp[i-1];
while(j&&a[i]!=a[j])
j=kmp[j-1];
if(a[i]==a[j])
j++;
kmp[i]=j;
}
while(kmp[len-1])
ans[++cnt]=kmp[len-1],kmp[len-1]=kmp[kmp[len-1]-1];
for(int i=cnt;i>=1;i--)
printf("%d ",ans[i]);
printf("%d\n",len);
}
}
}
int main()
{
Acheron::Awork();
return Ratio;
}
上一张思路图

其中,\(next[i]\),\(next[next[i]]\),\(next[next[next[i]]]······\)都是这个前缀串i的公共前后缀,而且只有它们是公共前后缀。
那么,我们只要在求\(next\)的过程中,顺便把这个公共前后缀的数量递推一下,就得到了一个弱化版的\(num\)数组:可以重叠的公共前后缀数量。
\(next\)数组的性质之一:\(next[i]<i\)
也就是说,当\(next\)递归至比原前缀i的长度的一半要小时,那么这个\(next\)的递推出的答案\(ans\)就是\(i\)的\(num\)值了。
谨慎,但也无需胆怯
#include<bits/stdc++.h>
#define fo(x,y,z) for(register int (x)=(y);(x)<=(z);(x)++)
#define fu(x,y,z) for(register int (x)=(y);(x)>=(z);(x)--)
typedef long long ll;
typedef unsigned long long ull;
namespace Aventurine
{//快读 快写 快换行、空格输出
inline ull qr()
{
char ch=getchar();ull x=0,f=1;
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
return x*f;
}
inline void qw(int x)
{
if(!x)
return;
qw(x/10);
putchar(x%10+'0');
}
inline void qhh(int x)
{
qw(x);
putchar('\n');
}
inline void qkg(int x)
{
if(x==0)
putchar('0');
else
qw(x);
putchar(' ');
}
}
using namespace std;
using namespace Aventurine;
#define qr qr()
const int Ratio=0;
const int N=1000005;
const int maxi=INT_MAX;
const ll mod=1e9+7;
char a[N];
int len,kmp[N],ans[N];
ll cnt;
namespace Acheron
{
void Akmpprepare()
{
int j=0;
ans[0]=0,ans[1]=1;
fo(i,2,len)
{
while(j&&a[i]!=a[j+1])
j=kmp[j];
if(a[i]==a[j+1])
j++;
kmp[i]=j;
ans[i]=ans[j]+1;
}
}
void Awork()
{
int j=0;
fo(i,2,len)
{
while(j&&a[i]!=a[j+1])
j=kmp[j];
if(a[i]==a[j+1])
j++;
while((j<<1)>i)
j=kmp[j];
cnt=(cnt*(ll)(ans[j]+1))%mod;
}
}
void Aput()
{
printf("%lld\n",cnt);
}
}
int main()
{
int T=qr;
while(T--)
{
scanf("%s",a+1);
len=strlen(a+1);
cnt=1;
Acheron::Akmpprepare();
Acheron::Awork();
Acheron::Aput();
}
return Ratio;
}
KMP
思路
KMP的精髓在于,对于每次失配之后,我们不会从头重新开始枚举,而是根据我们已知的数据,从某个特定的位置开始匹配。
而对于模式串的每一位,都有唯一的特定变化位置,这个在失配之后的特定变化位置可以帮助我们利用已有的数据不用从头匹配,从而节约时间。
具体例子可以自己手搓一个。
两个关键点:
1.我们的失配数组应当建立在模式串意义下,而不是文本串意义下。因为显然模式串要更加灵活,在失配后换位时,可以更灵活简便地处理。
2.移位法则:在模式串\(s\)中,对于每一位\(si\) ,它的\(kmp\)数组应当是记录一个位置 \(j\),\(j \leq i\)并且满足\(s_i=s_j\),并且在\(j!=1\)时满足\(s_1\)至\(s_{j-1}\)分别与\(s_{i-j+1}\)~\(s_{i-1}\)按位相等。
代码
const int Ratio=0;
const int N=1000005;
char a[N],b[N];
int lena,lenb,kmp[N];
namespace Acheron
{
void Akmpprepare()
{//kmp-即失配回跳到哪个位置的数组-初始化
int j=0;
fo(i,2,lenb)
{
while(j&&b[i]!=b[j+1])//模式串自配 判0:回跳到头
j=kmp[j];
if(b[j+1]==b[i])
j++;
kmp[i]=j;
}
}
void Akmpwork()
{
int j=0;
fo(i,1,lena)
{
while(j>0&&b[j+1]!=a[i])//失配回跳
j=kmp[j];
if(b[j+1]==a[i])//匹配成功:对应模式串位置+1
j++;
if(j==lenb)
{
printf("%d\n",i-lenb+1);
j=kmp[j];
}
}
}
void Aput()
{
fo(i,1,lenb)
qkg(kmp[i]);
}
}
int main()
{
scanf("%s%s",a+1,b+1);
lena=strlen(a+1),lenb=strlen(b+1);
Acheron::Akmpprepare();
Acheron::Akmpwork();
Acheron::Aput();
return Ratio;
}
思路
关键点在于想出能将删去后的字符串拼接在一起而且能继续匹配的方法...
对了,用\(\huge{栈}\)。
先正常跑一遍\(KMP\,\)板子,然后每次匹配时记录下在\(a_i\)失配时回跳到的下标\(akmp_i\),当\(j=lenb\),即模式串被完全匹配后,删除栈内\(lenb\)个元素,将\(j\)回跳,最后反向输出栈内元素(原因是从站内取的总是最新放入,即最后的元素;因此我们可以使用双端队列优化)。
注意!
一个例子:\(a\)串为danhengyinyue,\(b\)串为danheng时,完全匹配后栈内没有元素了,这时候应将\(j\)回跳至\(0\),代码表示应为:
if(s.size())
j=akmp[s.top()];
else
j=0;
因为这个被卡掉12pts的我。。。
霄龙现影,破!
const int Ratio=0;
const int N=1000005;
char a[N],b[N],ans[N];
int lena,lenb;
int kmp[N],akmp[N];
std::stack<int>s;
namespace Acheron
{
void Aprepare()
{
int j=0;
fo(i,2,lenb)
{
while(j&&b[i]!=b[j+1])
j=kmp[j];
if(b[j+1]==b[i])
j++;
kmp[i]=j;
}
}
void Awork()
{
int j=0;
fo(i,1,lena)
{
while(j&&a[i]!=b[j+1])
j=kmp[j];
if(a[i]==b[j+1])
j++;
akmp[i]=j;
s.push(i);
if(j==lenb)
{
fo(i,1,lenb)
s.pop();
if(s.size())
j=akmp[s.top()];
else
j=0;
}
}
}
void Aput()
{
int siz=s.size();
fo(i,1,siz)
ans[i]=a[s.top()],s.pop();
fu(i,siz,1)
printf("%c",ans[i]);
}
}
int main()
{
scanf("%s%s",a+1,b+1);
lena=strlen(a+1),lenb=strlen(b+1);
Acheron::Aprepare();
Acheron::Awork();
Acheron::Aput();
return Ratio;
}
$Updating...$

随机推荐
- 财务人提高竞争力必备的技能,怎么能少了ta!
从近年来大数据技术的发展趋势和相关产业飞速发展的状态,可以看出当前"数据分析"的热度可以说是有增无减,而且从市场上对数据分析人才的需求缺口也可以看出企业对数据分析的重视程度.未来随 ...
- MogDB/opengauss触发器简介(1)
MogDB/opengauss 触发器简介(1) 触发器是对应用动作的响应机制,当应用对一个对象发起 DML 操作时,就会产生一个触发事件(Event).如果该对象上拥有该事件对应的触发器,那么就会检 ...
- IP路由的工作原理
一.路由 路由在网络中起到什么作用? • 路由器负责将数据报文在IP网段之间进行转发 • 路由是指导路由器如何进行数据转发的路径信息 IP之间连通的前提是什么? • 沿途的每台路由器上都有到达目的网段 ...
- CentOS7环境saltstack安装配置
一.安装epel yum源 yum -y install epel-release yum clean all yum makecache 二.安装 saltstack-master 并配置 1.安装 ...
- Win7 局域网服务器 - FTP 服务器搭建指南
1. 打开 "开始" 菜单,找到控制面板 2. 选择 "程序" 3. 选择 "打开或关闭 Windows 功能" 4. 选择 "I ...
- nginx重新整理——————http请求的11个阶段中的preaccess[十四]
前言 简单整理一下preaccess. 正文 主要是介绍一下上文提及到的limit_req以及limit_conn. 里面是http_limit_conn_module 默认编译进去. 生效范围: 全 ...
- redis 简单整理——客户端常见异常[十七]
前言 这个还是比较常见的,也就是比较对开发有用的部分. 正文 1.无法从连接池获取到连接 JedisPool中的Jedis对象个数是有限的,默认是8个.这里假设使用的默 认配置,如果有8个Jedis对 ...
- 面向切面编程AOP[二](java @EnableAspectJAutoProxy 代码原理)
前言 @EnableAspectJAutoProxy 是启动aop功能的意思,那么里面是什么呢? 正文 @Target({ElementType.TYPE}) @Retention(Retention ...
- seo FAQ 科普
前言 seo 常规问题整理,后续不断补充,只做科普. 正文 1.什么是seo: seo 也就是搜索排名. 2.百度,360等知名品牌如何排名: 抓取,过滤,建立索引,输出结果 3.是否网站越好看排名就 ...
- redis 一百二十篇(历史发展)之第二篇
正文 简介: Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库. Redis 与其他 key - value 缓存产品有以下三个特点: Redis支持数据的持久化, ...

