题目大意
题目首先花了大量篇幅介绍了KMP算法。其中包括回退数组nextnextnext:
- 对于字符串SSS的前iii个字符构成的子串,既是它的后缀又是它的前缀的字符串中(它本身除外),最长的长度记作next[i]next[i]next[i]。
- 例如SSS为abcababcabcababcabcababc,则next[5]=2next[5]=2next[5]=2。因为SSS的前555个字符为abcababcababcab,ababab既是它的后缀又是它的前缀,并且找不到一个更长的字符串满足这个性质。同理,还可得出next[1]=next[2]=next[3]=0next[1]=next[2]=next[3]=0next[1]=next[2]=next[3]=0,next[4]=next[6]=1next[4]=next[6]=1next[4]=next[6]=1,next[7]=2next[7]=2next[7]=2,next[8]=3next[8]=3next[8]=3。
而本题要求求出一个numnumnum数组:
- 对于字符串SSS的前iii个字符构成的子串,既是它的后缀同时又是它的前缀,并且该后缀与该前缀不重叠,将这种字符串的数量记作num[i]num[i]num[i]。
- 例如SSS为aaaaaaaaaaaaaaa,则num[4]=2num[4]=2num[4]=2。这是因为SSS的前444个字符为aaaaaaaaaaaa,其中aaa和aaaaaa都满足性质“既是后缀又是前缀”,同时保证这个后缀与这个前缀不重叠。而aaaaaaaaa虽然满足性质“既是后缀又是前缀”,但是这个后缀与这个前缀重叠了,所以不能计算在内。同理,num[1]=0num[1]=0num[1]=0,num[2]=num[3]=1num[2]=num[3]=1num[2]=num[3]=1,num[5]=2num[5]=2num[5]=2。
字符串的长度很大(L⩽1000000L\leqslant 1000000L⩽1000000),结果输出∏i=1L(num[i]+1)mod 1000000007\prod\limits_{i=1}^L(num[i]+1)\mod 1000000007i=1∏L(num[i]+1)mod1000000007。
思路
numnumnum数组中“后缀与前缀不重叠”的条件不容易处理,并且跟nextnextnext数组的定义大相径庭,故定义一个num′num'num′数组:
- 对于字符串SSS的前iii个字符构成的子串,既是它的后缀同时又是它的前缀的字符串的数量记作num[i]num[i]num[i]。这里的前缀和后缀包括SSS本身。
其实就是去掉“后缀与前缀不重叠”这个条件的numnumnum数组。
num′[i]num'[i]num′[i]中统计的串中,首先一定有这个串本身;而其它的串都被囊括在next[i]next[i]next[i]表示的串内(包括两个长度为next[i]next[i]next[i]的串):
根据nextnextnext数组的定义,首尾两个长度为next[i]next[i]next[i]的串相同,把所有的串平移至同一个长度为nextnextnext的串内:
这些串就成了前next[i]next[i]next[i]个字符构成的子串中既是后缀又是前缀的字符串,根据定义,这些串的数量为num′[next[i]]num'[next[i]]num′[next[i]]。
再加上前iii个字符构成的子串本身,得出递推式:
num′[i]=num′[next[i]]+1num'[i]=num'[next[i]]+1num′[i]=num′[next[i]]+1
递推的时间复杂度是O(n)O(n)O(n)(这里以及下文的nnn指字符串长度LLL,也就是numnumnum数组的大小)。递推的边界是num′[0]=0num'[0]=0num′[0]=0。
现在考虑把“后缀与前缀不重叠”的条件加上。这个条件就是让num[i]num[i]num[i]中的串的长度不超过i2\frac i22i。
为了让字符串既是后缀又是前缀,除了串本身以外,最长的字符串就是next[i]next[i]next[i]表示的串。若next[i]next[i]next[i]的长度已经不超过iii的一半,则num[i]num[i]num[i]就是num′[next[i]]num'[next[i]]num′[next[i]]。若next[i]next[i]next[i]仍不满足要求,根据前面的平移操作和递推,下一个最长的串的长度就应该是next[next[i]]next[next[i]]next[next[i]],一直回退下去,直到串的长度为满足条件的最大的长度jjj。然后就有
num[i]=num′[j]num[i]=num'[j]num[i]=num′[j]
不难得到下面的代码:
for(int i=1;i<=n;i++){
int j=next[i];
while(j*2>i)j=next[j];
num[i]=num'[j];
}
然而这个做法的时间复杂度是O(n2)O(n^2)O(n2)。不妨试试样例的第一组数据:aaaaaaaaaaaaaaa。
造成如此高的复杂度的原因是next[i]next[i]next[i]的规模是O(n)O(n)O(n)的,最坏情况下对于每个iii,jjj都要减小O(n)O(n)O(n)次,然后就爆了。
考虑用KMP求nextnextnext数组的过程:
for(int i=2,j=0;i<=len;i++){
while(j&&t[j+1]!=t[i])j=fail[j];
if(t[j+1]==t[i])j++;fail[i]=j;
}
KMP的时间复杂度是O(n)O(n)O(n)的,因为iii从111枚举到nnn,增加O(n)O(n)O(n)次;除了if
语句中jjj增加O(n)O(n)O(n)次,其余时间jjj都在减少,所以jjj减少也不会超过O(n)O(n)O(n)次。
于是可以把上面的暴力修改成下面的代码:
for(int i=2,j=0;i<=len;i++){
while(j&&t[j+1]!=t[i])j=fail[j];
if(t[j+1]==t[i])j++;
while(j*2>i)j=fail[j];
num[i]=num'[j];
}
几个问题:
时间复杂度?
iii的枚举规模仍然是O(n)O(n)O(n),jjj还是在if
语句处增加O(n)O(n)O(n)次,因此时间复杂度为O(n)O(n)O(n)。
jjj是否是可行解(是否存在长度为jjj的串既是后缀又是前缀)?
jjj是由nextnextnext数组递推得到的,根据前面的递推,jjj是可行解。
jjj是否是最优解(jjj是否是不超过i2\frac i22i的最大可行解)?
由于jjj在当前的iii时要回退减小(第二个while
语句),iii增加111时,jjj似乎就有可能不是最大的可行解。
根据前面得知,jjj从next[i]next[i]next[i]处回退一定不会错过最优解。若jjj不继续回退,得到的jjj就是next[i]next[i]next[i],不会错过最优解。现在jjj要继续回退,轮到下一个i′=i+1i'=i+1i′=i+1时,为了当前子串的长度为j′j'j′的前后缀匹配,jjj原本就要回退(第一个while
语句)。
- 若此次回退后j′j'j′没有超过i′2\frac{i'}22i′,就有j′⩽i+12j'\leqslant\frac{i+1}2j′⩽2i+1。
- iii为偶数时,j′⩽i2j'\leqslant\frac i2j′⩽2i,因此在这之前把jjj回退到i2\frac i22i就没有什么影响。
- iii为奇数时,
- 若j′⩽i−12j'\leqslant\frac{i-1}2j′⩽2i−1,则在这之前把jjj回退到i2\frac i22i也没有什么影响。
- 若j′=i+12j'=\frac{i+1}2j′=2i+1,说明可以匹配的最长的前后缀刚好是子串的一半,根据KMP算法的原理,此时已匹配的长度j′j'j′一定是由上一次的jjj加111得来的。于是j=i−12j=\frac{i-1}2j=2i−1,同样没有影响。
- 若此次回退后j′j'j′超过了i′2\frac{i'}22i′,为了得到num[i′]num[i']num[i′]的值,j′j'j′仍然要回退,回到上一种情况。