BZOJ 4175: 小G的电话本 SAM+FFT
4175: 小G的电话本
Time Limit: 45 Sec Memory Limit: 256 MBSubmit: 195 Solved: 48
[Submit][Status][Discuss]
Description
小G是一个商人,他有一个电话本。电话本上记下了许多联系人,如timesqr、orzyhb等等。不过Tony对其中的某个联系人的名字S特别感兴趣,他从中提取出了这个联系人的名字中的所有片段,如提取出orz的 o、r、z、or、rz、orz等等。现在他想请你统计有多少个长度为k的片段对(P[1], P[2], P[3], ..., P[k]),使得在该片段对中所有片段在S中出现次数之和为他的幸运数m?注意两个片段对不同当且仅当两个片段对的某一位的片段不同,两个片段不同当且仅当这两个片段在S中的位置不同
Input
第一行两个整数k和m,意义见题目描述;第二行给出一个字符串表示Tony喜欢的联系人名字S。
Output
输出一行一个整数ans,表示答案模1005060097。
Sample Input
3 4
aaaaa
aaaaa
Sample Output
6
HINT
【样例解释】
符合要求的片段对一共有6种(用[p]s表示起始位置为p的s片段):
([1]aaaa, [1]aaaaa, [1]aaaaa)、([1]aaaaa, [1]aaaa, [1]aaaaa)、
([1]aaaaa, [1]aaaaa, [1]aaaa)、([2]aaaa, [1]aaaaa, [1]aaaaa)、
([1]aaaaa, [2]aaaa, [1]aaaaa)、([1]aaaaa, [1]aaaaa, [2]aaaa)。
【数据范围】
设n表示联系人的名字的长度,联系人的名字只包含小写字母。
对于10%的数据,1 <= n <= 100, k = 1。
对于40%的数据,1 <= n <= 100, k = 2。
对于70%的数据,1 <= n <= 100000, 1 <= k <= 10。
对于100%的数据,1 <= n <= 100000, 1 <= k <= 100000, 1 <= m <= n。
题意:
选出k个可以相同的子串,使他们在S中出现次数之和为m。求方案数。(看样例读题系列。
想法:
弱化版:在数集{S}中选k个可以重复的数字,使他们之和为m,求方案数。
设b[i]表示数值为i的个数,C[i]为其之和为i的方案数。
当k=1时,C=b,输出C[m]
当k=2时,C[i]=∑b[j]*b[i-j]
当k=3时,C[i]=∑b[j]*b[i-j],C[i]=∑C[j]*b[i-j]
......
当k=x时,C[i]=∑b[j]*b[i-j],C[i]=∑C[j]*b[i-j]...C[i]=∑C[j]*b[i-j] 共x-1次卷积
所以C=b^k,FFF+快速幂O(nlog^2n) ps:有更快的方法
回到原题,b[i]表示出现i次的子串个数,建出SAM后遍历每个节点:
b[|right(i)|]+=(sam[i].max-sam[i].min+1)*|right(i)|
总O(nlog^2n)
#include<cstdio> #include<cstring> #define ll long long const int len(100000),MP(1005060097),g(5),lem(262144); struct SamNode{int nx[26],pre,right,step;}sam[len*2+10]; int top=1,root=1,now=1,last,lastson; int cnt[len+10],p[len*2+10],b[len+10]; int n,m,k;char str[len+10]; void insert(int x) { last=now;now=++top;sam[now].step=sam[last].step+1;sam[now].right=1; for(;!sam[last].nx[x]&&last;last=sam[last].pre) sam[last].nx[x]=now; if(!last)sam[now].pre=root; else { lastson=sam[last].nx[x]; if(sam[lastson].step==sam[last].step+1)sam[now].pre=lastson; else { ++top; memcpy(sam[top].nx,sam[lastson].nx,sizeof(sam[top].nx)); sam[top].pre=sam[lastson].pre; sam[top].step=sam[last].step+1; sam[lastson].pre=sam[now].pre=top; for(;sam[last].nx[x]==lastson&&last;last=sam[last].pre) sam[last].nx[x]=top; } } } void update() { for(int i=1;i<=top;i++)cnt[sam[i].step]++; for(int i=1;i<=n;i++)cnt[i]+=cnt[i-1]; for(int i=top;i>=1;i--)p[cnt[sam[i].step]--]=i; for(int i=top;i>=1;i--) { sam[sam[p[i]].pre].right+=sam[p[i]].right; b[sam[p[i]].right]=(b[sam[p[i]].right]+1ll*sam[p[i]].right*(sam[p[i]].step-sam[sam[p[i]].pre].step))%MP; } } int R[lem+10],w[lem+10],wn,il,l,h; void swap(int &a,int &b){if(a==b)return;a^=b;b^=a;a^=b;} int power(int a,int b) { if(b<0)b=MP-1+b; long long t=1,y=a; while(b){if(b&1)t=(t*y)%MP;y=(y*y)%MP;b>>=1;} return (int)t; } struct Arr { void NTT(int *a,int l,int ty) { for(int i=0;i<l;i++) if(i<R[i])swap(a[i],a[R[i]]); for(int leng=2;leng<=l;leng<<=1) { int M=leng>>1; wn=power(g,ty*((MP-1)/leng)); for(int i=1;i<M;i++)w[i]=(1ll*w[i-1]*wn)%MP; for(int i=0;i<l;i+=leng) { for(int j=0;j<M;j++) { int x=a[i+j],y=(1ll*w[j]*a[i+j+M])%MP; a[i+j]=x+y;a[i+j+M]=x-y; a[i+j]-=a[i+j]>=MP?MP:0; a[i+j+M]+=a[i+j+M]<0?MP:0; } } } if(ty==-1) for(int i=0;i<l;i++)a[i]=(1ll*a[i]*il)%MP; } int a[lem+10];//只保留前m项 void mul(Arr &C) { NTT(a,l,1); NTT(C.a,l,1); for(int j=0;j<l;j++)a[j]=(1ll*a[j]*C.a[j])%MP; NTT(a,l,-1);for(int j=m+1;j<l;j++)a[j]=0; } }X,Y,C; void deal() { l=1;while(l<=m+m+1)l<<=1,h++; for(int i=0;i<l;i++)R[i]=(R[i>>1]>>1|(i&1)<<(h-1)); il=power(l,MP-2); } int main() { // freopen("C.in","r",stdin); // freopen("C.out","w",stdout); scanf("%d%d",&k,&m); scanf("%s",str);n=strlen(str); for(int i=0;i<n;i++)insert(str[i]-'a'); update();deal();w[0]=1; X.a[0]=1;for(int i=0;i<=m;i++)Y.a[i]=b[i];//,printf("%d\n",b[i]) for(;k;k>>=1,C=Y,Y.mul(C)) { // printf("Y\n"); // for(int i=0;i<=m;i++)printf("%d\n",Y.a[i]); if(k&1)C=Y,X.mul(C); // printf("X\n"); // for(int i=0;i<=m;i++)printf("%d\n",Y.a[i]); } printf("%d",X.a[m]); return 0; }