[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; }
|