刷题总结:
[kuangbin带你飞]专题十六 KMP & 扩展KMP & Manacher [Cloned]
A .KMP模板题
- 注意要用int 数组存文本串和模式串。
- 注意KMP算法的一些细节处理。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MA=1e6+5;
int p[MA];
int m[MA];
int n,w;
ll Next[MA];
void get_next()
{
int i=0;
int j=-1;
Next[0]=-1;
while(i<w){
if(j==-1||m[i]==m[j]){
i++;
j++;
Next[i]=j;
}
else j=Next[j];
}
}
void KMP()
{
get_next();
int i=0;
int j=0;
while(i<n&&j<w){
if(j==-1||p[i]==m[j]){
i++;
j++;
}
else j=Next[j];
}
if(j==w) cout<<i-w+1<<endl;
else cout<<"-1"<<endl;
}
int main()
{
int T;
scanf("%d",&T);
while(T--){
scanf("%d %d",&n,&w);
for(int i=0;i<n;++i)scanf("%d",&p[i]);
for(int i=0;i<w;++i)scanf("%d",&m[i]);
memset(Next,0,sizeof(Next));
KMP();
}
return 0;
}
B. KMP水题
- 换为字符串数组
C. KMP模板题
- 注意匹配位置不能重复(换一种写法)
int KMP()
{
get_next();
int i=0;
int j=0;
int ans=0;
while(i<len_p){
while(j!=-1&&p[i]!=m[j])j=Next[j];
i++;
j++;
if(j==len_m){
ans++;
j=0;//每次清空匹配情况。
}
}
return ans;
}
D. KMP找循环节
参考博客
题意: 给一段字符串,让你添加一些字符,使得字符串可以看作是由一段字符串不断重复得到的。问添加字符数量最小是多少?
分析:
- 很明显是循环节问题,也就想到KMP算法求循环节,这好像是KMP的一个应用。
- 用 get_next() 得到 next[ ] 数组,保存[ 0 ~ i-1]中最长公共前后缀长度。
- i - next [ i ] 为最小循环节长度,如果i %(i - next[ i ])==0说明字符串是循环的,不需要添加字符串,但是我们要注意如果next[ i ] = 0的情况呢(也就是类似 absdwe 这种字符串,如果直接按公式 i % ( i - next [ i ] ) 计算结果是0,但这并不是循环)所以我们要加上限制条件(next [ i ]!=0)
- [ 0 ~ i ]的字符串:
- 如果满足 i % ( i - next[i] ) == 0 && next[i] != 0,就说明字符串循环
- 计算最小循环节长度:len = i - next [ i ]
- 计算最小循环节重复次数: i / ( i - next [ i ] )
- 需要增加的个数为(好理解版 ):最小循环节长度减去总长度对最小循环节长度取余剩下的长度之差 (详见下面博客和图片 ) : ( len - len_m%len )%len
- 需要增加个数为 len - Next[ len_m ]%len ( len 就是最小循环节长度 ),
看懂增加个数的博客: https://blog.csdn.net/jadeyansir/article/details/77275203
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MA=1e6+5;
char p[MA];
char m[MA];
int len_p,len_m;
ll Next[MA];
void get_next()
{
int i=0;
int j=-1;
Next[0]=-1;
while(i<=len_m){
if(j==-1||m[i]==m[j]){
i++;
j++;
Next[i]=j;
}
else j=Next[j];
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--){
scanf("%s",&m);
len_m=strlen(m);
memset(Next,0,sizeof(Next));
get_next();
int length=len_m-Next[len_m];
if(len_m%length==0&&Next[len_m])cout<<"0"<<endl;
else cout<<length-Next[len_m]%length<<endl;
}
return 0;
}
E . 循环节水题
- 如上题,求next[ ] 遍历字符串,如果[ 0 ~ i ] 有循环节,就输出 i 和 循环节重复次数。
//部分代码
while(scanf("%d",&n)&&n){
num++;
scanf("%s",&s);
len_m=strlen(s);
get_next();
printf("Test case #%d\n",num);
for(int i=1;i<=len_m;++i){
if(i%(i-net[i])==0&&net[i]!=0){
printf("%d %d\n",i,i/(i-net[i]));
}
}
cout<<endl;
}
F .POJ 2406
- 题意:找最小循环节的重复次数。
- 分析:
- 如果字符串长度对最小循环节长度取模为0,就是整除。如ab ab ab ab可以被 ab 长度上整除。并且满足字符串一定有相等的前后缀(next[ len_m ] > 0)那么就输出字符串长度除以最小循环节长度。其他情况( abcde , abcabcab )都只能由一个字符串重复一次得到。
- 如果len_m % ( len - next[ len_m ] ) == 0 且next[ len_m ]>0 就输出 len_m / ( len - next[ len_m ])。其他情况输出 1。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int MA=1e6*5+5;
char m[MA];
int len_m;
int net[MA];
void get_next()
{
int i=0;
int j=-1;
net[0]=-1;
while(i<=len_m){
if(j==-1||m[i]==m[j]){
i++;
j++;
net[i]=j;
}
else j=net[j];
}
}
int main()
{
while(scanf("%s",&m)){
if(m[0]=='.')break;
len_m=strlen(m);
memset(net,0,sizeof(net));
get_next();
int length=len_m-net[len_m];
if(len_m%length==0&&net[len_m])cout<<len_m/length<<endl;
else cout<<"1"<<endl;
}
return 0;
}