【NOI2014】动物园
这道题因为自己把1000000007写成了100000007而浪费了三个小时,所以告诫自己:数一下几位!数一下几位!数一下几位!
先贴代码~
1 #include<cstdio> 2 #include<cstring> 3 #define mod 1000000007 //该看眼科了... 4 #define LL long long 5 int next[1000005],cnt[1000005],n; 6 LL ans; //cnt[i]表示前i个字符公共前后缀的数目,包括自己!也就是说cnt[i]至少为1(i!=0)(至于为什么要这样做,在(2)中解释) 7 char s[1000005]; 8 void getcnt(int l){ //getcnt:获取常规的next[],同时得到cnt[] ——(#) 9 next[0]=-1; 10 cnt[0]=0; 11 int i=0,k=-1; 12 while(i<l){ 13 while(k>=0&&s[k]!=s[i]) k=next[k]; 14 i++,k++; 15 next[i]=k; 16 cnt[i]=cnt[k]+1; 17 } 18 } 19 void solve(int l){ //solve:大体上是再求一遍next[]的过程,每次可以得到要求的num[],这里有两个问题: 20 ans=1; //1.为什么不能在最外层while每次开始的时候用k=next[i],而要再像求next[]那样求一遍,直接用不会更快吗? ——(1) 21 int k=-1,i=0; 22 while(i<l){ //2.num[i]是怎样通过cnt[i]得到的? ——(2) 23 while(k>=0&&s[k]!=s[i]) k=next[k]; 24 k++,i++; 25 while(2*k>i) k=next[k]; 26 ans=ans*(LL)(cnt[k]+1)%mod; 27 } 28 printf("%lld\n",ans); 29 } 30 int main() 31 { 32 scanf("%d",&n); 33 while(n--){ 34 scanf("%s",s); 35 int l=strlen(s); 36 getcnt(l); 37 solve(l); 38 } 39 }
现在来一一解释上面的三个地方。
题目要求的是数量,自然就可以想到next[]数组的意义:最长公共前后缀长度,也就是next[i]即前i个字符的最长公共前后缀长度。
现在我们假定对i已经知道了next[i](现在只把它看成一个数字),注意到前i个字符的前next[i]个和后next[i]个字符是相等的,我们要求的是所有的公共前后缀,既然next[i]是最长的一个,又注意到next[next[i]]是前next[i]个字符的最长公共前后缀,从而我们可以推出,前i个字符中的前next[next[i]]和后next[next[i]]个字符也是相同的。也就说,我们可以递推地求解前i个字符的公共前后缀数量,由于next[]数组的最长性质,我们保证一定可以取遍所有的公共前后缀。实际上这也是用next[]数组求解周期串问题的核心思想。
现在我们可以解答(#)了,假设我们已经知道k=next[i](始终把next[i]看成一个数字,就像定积分是一个数字一样(逃...)和前i-1个字符每个字符的cnt[]值(这个是我们递推的依据),从而可以得到:
cnt[i]=cnt[k]+1
cnt[k]是好理解的,就是前k个字符的所有公共前后缀数量(也就是我们现在查考的前i个字符的最长公共前后缀[0,...,k-1]的公共前后缀数量),由上面的分析我们知道了,前k个字符的公共前后缀就是前i个字符的公共前后缀,再加上前i个字符本身(为什么要加自己?这样不就不满足长度不超过i/2了吗?这个问题留到(2)解答),就得到了上面的递推式,从而可以通过初始化cnt[0]=0(显然不存在前0个字符这种说法的嘛)而得到所有的cnt[i],1<=i<=strlen(s)
下面来解答(1)和(2)这两个问题。
问题(1)其实是比较好回答的,如果每次循环开始都直接用k=next[i],也就是先得到之前求出的最长公共前后缀,那么显然k是可能很大的,这时候要把它减少到<=i/2的长度可能需要经过很长的步骤(不妨就考虑单个字符序列aaaaaaaaaaaaa……,它浪费在k上的时间是巨大的)!也就是说,我们再一次从开头进行一遍求next[]的过程其实并不是重复求next[]值(注意到在求next[]的过程中,我们始终是在维护最大的k值),而是类似的,维护最大的k'值,这个值,也就是不超过i/2且是前i个字符的最长公共前后缀长度(注意是<=i/2,而不是next[i],因为next[i]没有限定长度)。同样的仿照next[]的求法,我们当然可以递推地求出每一个i对应的k'值。
问题(2)就是如何维护k'(下面对这个k'就用k表示),从而能求出num[]。假如我们已经求出了num[i-1],从(1)可以看出,它比next[]的k多限制了一个长度不超过i/2的条件,于是就可以像求next[i]那样一直递推到满足s[i]==s[k]||k>=0为止,这时候如果2*k>i,那么就违反了上述限制,从而还要继续令k=next[k],直到2*k<=i(还记得吗,next[next[k]]也是前i个字符的前后缀),这里就出现了在(1)中提到的效率问题。由于在i-1时k已经满足2*k<=i了,就算再多加一个字符,也至多再进行一次k=next[k]就能把长度控制在2*k<=i中,所以说,把第25行的while改为if也是可以的!那么为什么这样求出来的k对应的cnt[k]就是要求的num[i]的值呢?还记得我们的cnt[i]是包含了整个长度为i的串吗?这个问题请自己思考。