(KMP)[NOI2014] 动物园解析
以前oi的时候没好好学的算法,大学再学一遍,收获还是蛮大的
本题要求num[i]+1的乘积,其中num[i]是对于一个字符串的第i个前缀所具有的长度不大于i/2的(前缀函数求的那种)前缀的个数。
- 1
当然先kmp,发现可以暴力跳转,也就是kmp求得 pi[i](或者称之为next[i])后暴力枚举pi[pi[i]-1],直到长度满足要求,复杂度为O(n2)。
发现这个枚举很眼熟,可以通过倍增优化,时间复杂度降至O(nlgn)。
- 2
上面得到的是最大的满足要求的长度,而我们要求num。因为已经有了对应长度,这里我重新定义一下num,num[i]表示的是(前缀函数求的那种)前缀的个数(即去掉长度限制)。
发现num有递增性质,对任意j=pi[i],num[i]=num[j-1]+1,i.e.长度短的都符合条件,只是比j-1多了一个长一点的前缀。
- 3
对于 1 中求到的长度设为l,l-1就是对应的位置,所以对应数量由 2 得为num[l-1],所以答案就是这个num加1后得乘积咯。
- 4
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
using namespace std;
const int mx=1000005,maxn=20,mod=1000000007;
char s[mx];
int pi[22][mx],num[mx];
int l;
ll ans;
int main()
{
int t,p;scanf("%d",&t);
for(int op=1;op<=t;++op){
scanf("%s",s);l=strlen(s);ans=1;
memset(pi,0,sizeof(pi));
for(int i=1;i<l;++i){
int j=pi[0][i-1];
while(j>0&&s[i]!=s[j]) j=pi[0][j-1];
if(s[i]==s[j]) ++j;
pi[0][i]=j;
if(j) num[i]=num[j-1]+1;
else num[i]=0;
if(j==0) p=0;
else for(int k=1;k<=maxn;++k){
pi[k][i]=pi[k-1][pi[k-1][i]-1];
if(!pi[k][i]){
p=k-1;break;
}
}
j=i;
for(int k=p;k>=0;--k) if((pi[k][j]<<1)>i+1){
j=pi[k][j]-1;
}
ans*=(num[j]+1);ans%=mod;
}
printf("%lld\n",ans);
}
return 0;
}