「 「 「字符串算法 」 」 」第 3 3 3章 K M P KMP KMP 算法
目录:
A.子串查找
B.重复子串
C.周期长度和
D.子串拆分
A . A. A. 例题 1 1 1 子串查找
分析:
一道比 K M P KMP KMP模板还模板的题 人家 K M P KMP KMP模板还要求一个 b o r d e r border border 这个直接匹配就行了
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
//#pragma GCC optimize(2)
#define reg register
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=1e6+5;
int lena,lenb,ans,j,qwq[N];
char a[N],b[N];
int main(){
scanf("%s%s",a+1,b+1);
lena=strlen(a+1);
lenb=strlen(b+1);
j=0;
for(int i=2;i<=lenb;i++)
{
while(j&&b[i]!=b[j+1]) j=qwq[j]; //KMP模板
if(b[i]==b[j+1]) j++;
qwq[i]=j;
}
j=0;
for(int i=1;i<=lena;i++)
{
while(j&&a[i]!=b[j+1]) j=qwq[j];
if(a[i]==b[j+1]) j++;
if(j==lenb) ans++;
}
printf("%d",ans);
return 0;
}
B . B. B. 例题 2 2 2 重复子串
分析:
我们做完 K M P KMP KMP后 就代表 1 1 1 ~ q w q n qwq_n qwqn与 n − q w q n + 1 n-qwq_n+1 n−qwqn+1 ~ n n n是相匹配的
所以如果 n ≡ 0 ( m o d ( n − q w q n ) ) n≡0(mod(n-qwq_n)) n≡0(mod(n−qwqn)) 也就是 n n n m o d mod mod ( n − q w q n ) = 0 (n-qwq_n)=0 (n−qwqn)=0 则存在重复连续子串
这个子串的长度为 n − q w q n n-qwq_n n−qwqn 循环次数为 n / ( n − q w q n ) n/(n-qwq_n) n/(n−qwqn) 也就是题目要求的数量了
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
//#pragma GCC optimize(2)
#define reg register
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=1e6+5;
int lena,ans,qwq[N],j;
char a[N];
int main(){
scanf("%s",a+1);
lena=strlen(a+1);
while(lena!=1||a[1]!='.')
{
j=0;
for(int i=2;i<=lena;i++)
{
while(j&&a[i]!=a[j+1]) j=qwq[j]; //KMP
if(a[i]==a[j+1]) j++;
qwq[i]=j;
}
if(lena%(lena-qwq[lena])==0)
printf("%d\n",lena/(lena-qwq[lena])); //结论
else printf("1\n");
scanf("%s",a+1);
lena=strlen(a+1);
}
return 0;
}
C . C. C. 例题 3 3 3 周期长度和
分析:
大概意思是让你找到一个子串 复制一遍后包含原串 找这个子串长度最长
把原串分成两部分 让前面的包含后面的 周期就是前面的子串 ( ( (对着题面理解一下 ) ) )
这其实就是个 K M P KMP KMP
然后要使周期长 那么前面子串就要长 后面的就尽量短 而后面的子串是 K M P KMP KMP的长度
想让 K M P KMP KMP小 就是要找匹配少的 当 q w q i = 0 qwq_i=0 qwqi=0时 i i i是最小的 要找的就是这个
这个递归 q w q i qwq_i qwqi到 0 0 0 就可以用并查集优化了 把每个前缀 i i i保存下来累计
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
//#pragma GCC optimize(2)
#define reg register
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=1e6+5;
ll n,j,qwq[N],ans;
char a[N];
ll find(int x){
return (qwq[x])?qwq[x]=find(qwq[x]):x;} //并查集维护
int main(){
scanf("%lld",&n);
scanf("%s",a+1);
j=0;
for(reg int i=2;i<=n;i++)
{
while(j&&a[i]!=a[j+1]) j=qwq[j];
if(a[i]==a[j+1]) j++; //KMP
qwq[i]=j;
}
for(reg int i=1;i<=n;i++)
ans+=i-find(i); //统计
printf("%lld",ans);
return 0;
}
D . D. D. 例题 4 4 4 子串拆分
分析:
数据不大 直接跑 n 2 n^2 n2算法 枚举两边范围
看能否组成 A B A ABA ABA形 可以跑 K M P KMP KMP 直到两范围没有重合即可
这样做出来的 A A A是最长的 但一个串 S S S会有多种分配方法 要找其他的 就继续使 q w q i = i qwq_i=i qwqi=i
遇到一组不重合的就可以了 要不重合 就要使 q w q n ∗ 2 < n qwq_n*2<n qwqn∗2<n 指针一直回跳就行
这样是 n 3 n^3 n3的 然后先找出左端点 右端点就会在 K M P KMP KMP后包含 就不用枚举了
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
//#pragma GCC optimize(2)
#define reg register
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=2e4+5;
int n,m,qwq[N],ans,j,p;
char ch[N],d[N];
int main(){
scanf("%s",ch+1);
scanf("%d",&p);
n=strlen(ch+1);
for(reg int i=1;i<=n;i++)
{
memset(d,0,sizeof(d));
memset(qwq,0,sizeof(qwq));
for(reg int l=1;i+l-1<=n;l++)
d[l]=ch[i+l-1]; //处理左端点
m=strlen(d+1); j=0;
for(reg int l=2;l<=m;l++)
{
while(j&&d[l]!=d[j+1]) j=qwq[j]; //KMP
if(d[l]==d[j+1]) j++;
qwq[l]=j;
}
j=0;
for(reg int k=1;k<=m;k++)
{
while(j&&d[k]!=d[j+1]) j=qwq[j];
if(d[k]==d[j+1]) j++;
while((j<<1)>=k) j=qwq[j]; //不合法
if(j>=p) ans++; //满足要求
}
}
printf("%d",ans);
return 0;
}