扩展KMP的姿势
扩展KMP可以在
O(n)
的复杂度内求这样一个问题:
给定两个串
S,T
,设
n=|S|,m=|T|
求
S
中的每个后缀与
算法过程:
类似的,我们令
next[i]
表示
T
中的每个后缀与
令
k
表示当前
假设当前要计算
extend[i]
,易知
S[k..p]=T[1..p−k+1]
,
则有
S[i..p]=T[i−k+1,p−k+1]
。
令
L=next[i−k+1]
,
若
L<p−i+1
,则
extend[i]=L
。
否则,先令
extend[i]=p−i+1
(注意
p−i+1
不能
<0
<script type="math/tex" id="MathJax-Element-7785"><0</script>,如果
<0
<script type="math/tex" id="MathJax-Element-7786"><0</script>就赋为
0
),然后从
算法结束后,如果当前
i+next[i]>k+next[k]
,则更新
k
。
求解
扩展KMP算法是十分优秀的,常见的应用有:
1.求最长公共前缀。
2.求解重复子串的长度:
如串
然后这道题,我们可以把原串补在后面一遍,然后求出
next
数组,若
next[i]>=len
,证明两个串相等,否则我们只需要比较
s[next[i]+1]
与
s[i+next[i]]
的大小,就能得出两个串的大小关系。
然后至于重复的串,我们可以用kmp来求一下最小循环节,将三个答案都除以最小循环节出现的个数就好了。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int a,b,c,len;
int p[100005],q[100005];
char s[200005];
int main()
{
int testcase;
scanf("%d",&testcase);
for (int T=1;T<=testcase;T++)
{
scanf("%s",s+1);
int j=0; p[1]=0;
len=strlen(s+1);
for (int i=2;i<=len;i++)
{
while (j&&s[j+1]!=s[i]) j=p[j];
if (s[j+1]==s[i]) j++;
p[i]=j;
}
int tmp=len%(len-p[len])==0?len/(len-p[len]):1;
for (int i=1;i<=len;i++) s[i+len]=s[i];
len<<=1;
int p=1;
q[1]=len;
while (p<=len&&s[p]==s[p+1]) p++;
q[2]=p-1;
int k=2;
for (int i=3;i<=len>>1;i++)
{
int p=k+q[k]-1;
q[i]=min(q[i-k+1],max(p-i+1,0));
while (i+q[i]<=len&&s[q[i]+1]==s[i+q[i]]) q[i]++;
if (i+q[i]>k+q[k]) k=i;
}
len>>=1;
a=b=c=0;
for (int i=1;i<=len;i++)
if (q[i]>=len) b++;
else if (s[q[i]+1]>s[i+q[i]]) a++;
else c++;
printf("Case %d: %d %d %d\n",T,a/tmp,b/tmp,c/tmp);
}
return 0;
}