题目描述
给定一个长为L的字符串,求一个num数组,num[i]表示长度为i的前缀中字符串S‘的数量,其中S‘既是该前缀的前缀也是该前缀的后缀,且|S‘|*2≤i,为了方便输出,只用输出(num[i]+1)的乘积
输入格式:
第1行仅包含一个正整数n ,表示测试数据的组数。
随后n行,每行描述一组测试数据。每组测试数据仅含有一个字符串S,S的定义详见题目描述。数据保证S 中仅含小写字母。输入文件中不会包含多余的空行,行末不会存在多余的空格。
输出格式:
包含 n 行,每行描述一组测试数据的答案,答案的顺序应与输入数据的顺序保持一致。对于每组测试数据,仅需要输出一个整数,表示这组测试数据的答案对 1,000,000,007 取模的结果。输出文件中不应包含多余的空行。
测试点编号约定
1 N ≤ 5, L ≤ 50
2 N ≤ 5, L ≤ 200
3 N ≤ 5, L ≤ 200
4 N ≤ 5, L ≤ 10,000
5 N ≤ 5, L ≤ 10,000
6 N ≤ 5, L ≤ 100,000
7 N ≤ 5, L ≤ 200,000
8 N ≤ 5, L ≤ 500,000
9 N ≤ 5, L ≤ 1,000,000
10 N ≤ 5, L ≤ 1,000,000
题解
暴力:求num[i]时暴力跳fail,将满足条件的计入
优化:利用fail树,倍增往前跳。
正解:求i位置的fail时,利用上一次的fail,转移方式与求fail数组相同,长度不满足再往前跳
性质:从一个位置起不管跳多少次fail,对应的前缀仍等于原位置的相等长度的后缀
对于cnt和fail的理解还值得思考
#include<bits/stdc++.h> using namespace std; #define ll long long const int maxn=1000005; const int mod=1000000007; int T,n; char s[maxn]; int fail[maxn],cnt[maxn];//cnt为fail为i的前缀的后缀等于前缀的个数 //fail[i]指向的是(0,i-1)的最大前缀等于最大后缀的后一个点 int main(){ scanf("%d",&T); while(T--){ //memset(fail,0,sizeof(fail)); //memset(cnt,0,sizeof(cnt)); ll ans=1; scanf("%s",s); n=strlen(s); fail[0]=fail[1]=0; cnt[1]=1;//根据定义fail能指向1,所以s[0]是该前缀的后缀,所以cnt[1]=1 int t; for(int i=1;i<n;i++){ t=fail[i]; while(t&&s[t]!=s[i]) t=fail[t]; if(s[i]==s[t]) t++; fail[i+1]=t; cnt[i+1]=cnt[t]+1;//递推, } //for(int i=0;i<n;i++) printf("%d ",cnt[i]); t=0; for(int i=0;i<n;i++){ while(t&&s[i]!=s[t]) t=fail[t];//利用上一次的fail找 if(s[i]==s[t]) t++; while((t<<1)>(i+1)) t=fail[t];//判断长度,i+1就是因为t指向的是最大前缀的下一位 ans=ans*(ll)(cnt[t]+1)%mod;//因为cnt的定义所以这里*cnt[t]是正确,而不是cnt[i] } printf("%lld ",ans); } }