P2375

[NOI2014] 动物园

似乎不同的人对失配指针的理解不同,结果弄的我比较晕。

这里明晰一下自己的定义。

$nxt(i)$ 表示 $[s,i)$ 这个区间的最长公共前缀后缀(非自身)的长度的数值。失配指针是其辅助作用。这里 $s$ 表示字符串的起始位置。至于配到那一位自己看着调整,都是很细节的。。。

这里采用 $s=1$。

这里直接在自动机上数数的复杂度比较高,大概是 $O(n^2)$ 的,我们采用递推。

先不急着直接搞出 $num$,我们先把重叠的情况一并统计进去,那么这个东西就可以递推了。

我们仍然定义 $num(i)$ 存着的是 $[1,i)$ 这个左闭右开区间的信息,那么显然在找到 $nxt(i)$ 后,$num(nxt(i+1))$ 是在 $num(i)$ 里的,而 $num(i)$ 仅仅多了它本身这个大的公共前缀后缀。所以说就有 $num(i)=num(nxt(i+1))+1$。

然后关于这个题目中的 $num$ 如何求,我们用模式串匹配模式串,然后匹配的长度超过长度一半我们就往前跳 $nxt$(注意此时仍然匹配),然后将这个 $num$ 作为需要求的 $num$ 值,然后加 1 相乘即可。

与 KMP 的时间复杂度证明同理,指针至多增加 $O(n)$ 次,所以至多减少 $O(n)$ 次,所以复杂度是 $O(n)$ 的。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;

const ll N=1e6,mo=1e9+7;

ll T,ans;
ll num[N+5],nxt[N+5];
char s[N+5];

inline void KMP_Pre(char *s,ll len) {
nxt[1]=0;nxt[2]=0;num[1]=0;num[2]=1;
for(ll i=2;i<=len;i++) {
ll j=nxt[i];
while(j&&s[i]!=s[j+1]) j=nxt[j+1];
j+=(s[i]==s[j+1]);nxt[i+1]=j;num[i+1]=num[j+1]+1;
}
}

inline ll read() {
ll ret=0,f=1;char ch=getchar();
while(ch<48||ch>57) {if(ch==45) f=-f;ch=getchar();}
while(ch>=48&&ch<=57) {ret=(ret<<3)+(ret<<1)+ch-48;ch=getchar();}
return ret*f;
}

inline void write(ll x) {
static char buf[22];static ll len=-1;
if(x>=0) {do{buf[++len]=x%10+48;x/=10;}while(x);}
else {putchar(45);do{buf[++len]=-(x%10)+48;x/=10;}while(x);}
while(len>=0) putchar(buf[len--]);
}

inline void writeln(ll x) {write(x);putchar('\n');}

int main() {

T=read();

while(T--) {
scanf("%s",s+1);ll n=strlen(s+1);
KMP_Pre(s,n);ans=1;ll j=0;
for(ll i=2;i<=n;i++) {
while(j&&s[i]!=s[j+1]) j=nxt[j+1];
j+=(s[i]==s[j+1]);while(j*2>i) j=nxt[j+1];
ans=ans*(num[j+1]+1)%mo;
}
writeln(ans);
}

return 0;
}