本题的hash解法
作者由于只有CSPJ水平,所以根本想不出KMP或者什么自动机解法。只会hash+二分
虽然该算法复杂度为O(Tnlogn),但是常数小,可以通过。。
考虑j>i,如果i~j这个字符串同时是串1-j的前缀和后缀,那么我们可以认为位置i对num[j]做出了1的贡献。
然后我们枚举i,看它可以对哪些num[j]产生贡献。容易发现如果k<j且i对num[j]产生了贡献,那么i也一定会对num[k]产生贡献。因此我们可以二分出一个x,使得i对num[i]->num[k]产生贡献,且不会对num[k+1]->num[n]产生贡献。
然后考虑二分过程中如何checki是否对当前的mid(我设的mid是j-i+1的值即前/后缀的长度)产生贡献:用hash判断一下字符1->mid组成的字符串和字符x->x+mid-1组成的字符串是否相同就可以了。然后统计答案就用差分数组乱搞一下就好了。
枚举i是o(n)的,二分是o(logn)的,所以总复杂度为o(Tnlogn),因为常数小可以通过。
上文中的->可以理解为~,因为作者对markdown太不了解所以只能这么写了。
排版很丑,表述很垃圾,代码也丑,希望轻喷。
code:
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5,mod=1e9+7,md=19260817;char c[N];int hash[N],n,pre[N],pows[N];
inline void work(int x){
if(c[x]!=c[1])return ;int l=1,r=min(x-1,n-x+1);
while(l<r){int mid=l+r+1>>1;
if((hash[x+mid-1]-(long long)hash[x-1]*(long long)pows[mid]%md+md)%md==hash[mid])l=mid;else r=mid-1;
}++pre[x];--pre[x+l];
}
signed main(){
int t;scanf("%d",&t);while(t--){
scanf("%s",c+1);n=strlen(c+1);pows[0]=1;memset(pre,0,sizeof(pre));
for(int i=1;i<=n;++i)hash[i]=(hash[i-1]*26+c[i]-'a')%md,pows[i]=pows[i-1]*26%md;
for(int i=2;i<=n;++i)work(i);for(int i=1;i<=n;++i)pre[i]+=pre[i-1];
long long ans=1;for(int i=2;i<=n;++i)ans=ans*((long long)pre[i]+1ll)%mod;
printf("%lld\n",ans);
}return 0;
}
代码中的hash就是hash值,pow[i]是26的i次方。然后pre是num的差分数组。个人认为代码很好理解。