HihoCoder 重复旋律
あの旋律を何度も繰り返しでも、あの日見た光景を再現できない
无论将那段旋律重复多少次,也无法重现那一日我们看到的景象
もし切ないならば、時をまきもどしてみるかい?
若是感到惆怅的话,要试着让时光倒流吗?
NONONOーー
今が最高!
以上部分请无视
hihocoder的一套后缀数组/后缀自动机模板(并不)题
敲了⑨遍后缀板子,也是带感
没有计时,不知道和隔壁某8′59′′比怎么样 2333
后缀数组一·重复旋律
描述
小Hi平时的一大兴趣爱好就是演奏钢琴。我们知道一个音乐旋律被表示为长度为 N 的数构成的数列。
小Hi在练习过很多曲子以后发现很多作品自身包含一样的旋律。旋律是一段连续的数列,相似的旋律在原数列可重叠。比如在1 2 3 2 3 2 1 中 2 3 2 出现了两次。
小Hi想知道一段旋律中出现次数至少为K次的旋律最长是多少?
输入
第一行两个整数 N和K。1≤N≤20000 1≤K≤N
接下来有 N 个整数,表示每个音的数字。1≤数字≤100
输出
一行一个整数,表示答案。
后缀数组模板题咯。
二分答案mid,扫描height数组,看大于mid的height有没有连续出现K-1次
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 #include<queue> 6 using namespace std; 7 const int mxn=100010; 8 int read(){ 9 int x=0,f=1;char ch=getchar(); 10 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 11 while(ch>='0' && ch<='9'){x=x*10-'0'+ch;ch=getchar();} 12 return x*f; 13 } 14 int sa[mxn],rk[mxn],ht[mxn],r[mxn]; 15 int wa[mxn],wb[mxn],wv[mxn],cnt[mxn]; 16 inline int cmp(int *r,int a,int b,int l){ 17 return r[a]==r[b] && r[a+l]==r[b+l]; 18 } 19 void GetSA(int *sa,int n,int m){ 20 int *x=wa,*y=wb; 21 int i,j; 22 // for(i=0;i<m;i++)cnt[i]=0; 23 for(i=0;i<n;i++)++cnt[x[i]=r[i]]; 24 for(i=1;i<m;i++)cnt[i]+=cnt[i-1]; 25 for(i=n-1;i>=0;i--)sa[--cnt[x[i]]]=i; 26 for(int j=1,p=0;p<n;j<<=1,m=p){ 27 for(p=0,i=n-j;i<n;i++)y[p++]=i; 28 for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j; 29 for(i=0;i<n;i++) 30 wv[i]=x[y[i]]; 31 for(i=0;i<m;i++)cnt[i]=0; 32 for(i=0;i<n;i++)++cnt[wv[i]]; 33 for(i=1;i<m;i++)cnt[i]+=cnt[i-1]; 34 for(i=n-1;i>=0;i--)sa[--cnt[wv[i]]]=y[i]; 35 swap(x,y); 36 p=1;x[sa[0]]=0; 37 for(i=1;i<n;i++) 38 x[sa[i]]=cmp(y,sa[i],sa[i-1],j)?p-1:p++; 39 } 40 /* printf("SA:\n"); 41 for(i=0;i<n;i++){ 42 printf("%d ",sa[i]); 43 } 44 puts("");*/ 45 return; 46 } 47 void GetHt(int n){ 48 for(int i=1;i<=n;i++)rk[sa[i]]=i; 49 int k=0; 50 for(int i=0,j;i<n;i++){ 51 j=sa[rk[i]-1]; 52 if(k)k--; 53 while(r[i+k]==r[j+k])k++; 54 ht[rk[i]]=k; 55 } 56 return; 57 } 58 int n,K; 59 bool calc(int lim){ 60 int tmp=1; 61 for(int i=1;i<n;i++){ 62 if(ht[i]>=lim)tmp++; 63 else tmp=1; 64 if(tmp>=K)return 1; 65 } 66 return 0; 67 } 68 int main(){ 69 int i,j; 70 n=read();K=read(); 71 for(i=0;i<n;i++)r[i]=read(); 72 GetSA(sa,n+1,105); 73 GetHt(n); 74 int l=0,r=n,ans=0; 75 while(l<=r){ 76 int mid=(l+r)>>1; 77 if(calc(mid)){ 78 l=mid+1; 79 ans=mid; 80 } 81 else r=mid-1; 82 } 83 printf("%d\n",ans); 84 return 0; 85 }
后缀数组二·重复旋律2
描述
小Hi平时的一大兴趣爱好就是演奏钢琴。我们知道一个音乐旋律被表示为长度为 N 的数构成的数列。小Hi在练习过很多曲子以后发现很多作品自身包含一样的旋律。
旋律可以表示为一段连续的数列,相似的旋律在原数列不可重叠,比如在1 2 3 2 3 2 1 中 2 3 2 出现了一次,2 3 出现了两次,小Hi想知道一段旋律中出现次数至少为两次的旋律最长是多少?
输入
第一行一个整数 N。1≤N≤100000
接下来有 N 个整数,表示每个音的数字。1≤数字≤1000
输出
一行一个整数,表示答案。
后缀数组
依旧是二分长度mid,然后看连续的一段大于等于mid的height中,sa[]最大和最小的相隔有没有超过mid
研究了一下,发现如果min和max要卡常的话,在括号内计算不多的情况下define的效果比inline函数好
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 #include<queue> 6 using namespace std; 7 const int mxn=100010; 8 int read(){ 9 int x=0,f=1;char ch=getchar(); 10 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 11 while(ch>='0' && ch<='9'){x=x*10-'0'+ch;ch=getchar();} 12 return x*f; 13 } 14 int sa[mxn],rk[mxn],ht[mxn],r[mxn]; 15 int wa[mxn],wb[mxn],cnt[mxn]; 16 inline int cmp(int *r,int a,int b,int l){ 17 return r[a]==r[b] && r[a+l]==r[b+l]; 18 } 19 void GetSA(int *sa,int n,int m){ 20 int i,j,p; 21 int *x=wa,*y=wb; 22 // for(i=1;i<=m;i++)cnt[i]=0; 23 for(i=1;i<=n;i++)++cnt[x[i]=r[i]]; 24 for(i=1;i<=m;i++)cnt[i]+=cnt[i-1]; 25 for(i=n;i;i--)sa[cnt[x[i]]--]=i; 26 for(j=1,p=0;p<n;j<<=1,m=p){ 27 for(p=0,i=n-j+1;i<=n;i++)y[++p]=i; 28 for(i=1;i<=n;i++)if(sa[i]>j)y[++p]=sa[i]-j; 29 for(i=1;i<=m;i++)cnt[i]=0; 30 for(i=1;i<=n;i++)++cnt[x[y[i]]]; 31 for(i=1;i<=m;i++)cnt[i]+=cnt[i-1]; 32 for(i=n;i;i--)sa[cnt[x[y[i]]]--]=y[i]; 33 swap(x,y); 34 p=1;x[sa[1]]=1; 35 for(i=2;i<=n;i++) 36 x[sa[i]]=cmp(y,sa[i],sa[i-1],j)?p:++p; 37 } 38 return; 39 } 40 void GetHT(int n){ 41 int i,j,k=0; 42 for(i=1;i<=n;i++)rk[sa[i]]=i; 43 for(i=1;i<=n;i++){ 44 if(rk[i]==1)continue; 45 if(k)--k; 46 j=sa[rk[i]-1]; 47 while(r[i+k]==r[j+k])k++; 48 ht[rk[i]]=k; 49 } 50 return; 51 } 52 #define amin(a,b) ((a)<(b)?(a):(b)) 53 #define amax(a,b) ((a)>(b)?(a):(b)) 54 int n; 55 bool solve(int lim){ 56 int mx=sa[1],mn=sa[1]; 57 for(int i=1;i<=n;i++){ 58 if(ht[i]>=lim){ 59 mx=amax(sa[i],mx); 60 mn=amin(sa[i],mn); 61 if(mx-mn>=lim)return 1; 62 } 63 else mx=mn=sa[i]; 64 } 65 return 0; 66 } 67 int main(){ 68 int i,j; 69 n=read(); 70 for(i=1;i<=n;i++)r[i]=read(); 71 GetSA(sa,n,1001); 72 GetHT(n); 73 int l=0,r=n,res=0; 74 while(l<=r){ 75 int mid=(l+r)>>1; 76 if(solve(mid)){ 77 res=mid; 78 l=mid+1; 79 }else r=mid-1; 80 } 81 printf("%d\n",res); 82 return 0; 83 }
后缀数组三·重复旋律3
描述
小Hi平时的一大兴趣爱好就是演奏钢琴。我们知道一个音乐旋律被表示为长度为 N 的数构成的数列。小Hi在练习过很多曲子以后发现很多作品中的旋律有共同的部分。
旋律是一段连续的数列,如果同一段旋律在作品A和作品B中同时出现过,这段旋律就是A和B共同的部分,比如在abab 在 bababab 和 cabacababc 中都出现过。小Hi想知道两部作品的共同旋律最长是多少?
输入
共两行。一行一个仅包含小写字母的字符串。字符串长度不超过 100000。
输出
一行一个整数,表示答案。
后缀数组模板题
LCP嘛
把两个串连起来,中间用特殊符号隔开
扫一遍height数组,ans=max(ans,height[i]) (sa[i]和sa[i-1]一个在前一个串里,一个在后一个串里)
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 #include<queue> 6 using namespace std; 7 const int mxn=200010; 8 int read(){ 9 int x=0,f=1;char ch=getchar(); 10 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 11 while(ch>='0' && ch<='9'){x=x*10-'0'+ch;ch=getchar();} 12 return x*f; 13 } 14 int sa[mxn],rk[mxn],ht[mxn],r[mxn]; 15 int wa[mxn],wb[mxn],cnt[mxn]; 16 inline int cmp(int *r,int a,int b,int l){ 17 return r[a]==r[b] && r[a+l]==r[b+l]; 18 } 19 void GetSA(int *sa,int n,int m){ 20 int *x=wa,*y=wb; 21 int i,j,p; 22 // for(i=0;i<m;i++)cnt[i]=0; 23 for(i=0;i<n;i++)cnt[x[i]=r[i]]++; 24 for(i=1;i<m;i++)cnt[i]+=cnt[i-1]; 25 for(i=n-1;i>=0;i--)sa[--cnt[x[i]]]=i; 26 for(j=1,p=0;p<n;j<<=1,m=p){ 27 p=0;for(i=n-j;i<n;i++)y[p++]=i; 28 for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j; 29 for(i=0;i<m;i++)cnt[i]=0; 30 for(i=0;i<n;i++)++cnt[x[y[i]]]; 31 for(i=1;i<m;i++)cnt[i]+=cnt[i-1]; 32 for(i=n-1;i>=0;i--)sa[--cnt[x[y[i]]]]=y[i]; 33 swap(x,y); 34 p=1;x[sa[0]]=0; 35 for(i=1;i<n;i++) 36 x[sa[i]]=cmp(y,sa[i],sa[i-1],j)?p-1:p++; 37 } 38 return; 39 } 40 void GetHT(int n){ 41 int i,j,k=0; 42 for(i=1;i<=n;i++)rk[sa[i]]=i; 43 for(i=0;i<n;i++){ 44 if(k)k--; 45 j=sa[rk[i]-1]; 46 while(r[i+k]==r[j+k])k++; 47 ht[rk[i]]=k; 48 } 49 return; 50 } 51 void solve(int n,int ed){ 52 int res=0; 53 for(int i=1;i<=ed;i++){ 54 if((sa[i]<=n && sa[i-1]>n)||(sa[i]>n && sa[i-1]<=n)) 55 res=max(res,ht[i]); 56 } 57 printf("%d\n",res); 58 return; 59 } 60 char s[mxn]; 61 int main(){ 62 int i,j; 63 scanf("%s",s); 64 int len=strlen(s); 65 for(i=0;i<len;i++)r[i]=s[i]-'a'+1; 66 r[len]=27; 67 int ed=len; 68 scanf("%s",s); 69 len=strlen(s); 70 for(i=0;i<len;i++)r[ed+i]=s[i]-'a'+1; 71 len+=ed; 72 GetSA(sa,len+1,28); 73 GetHT(len); 74 solve(ed,len); 75 return 0; 76 }
后缀数组四·重复旋律4
描述
小Hi平时的一大兴趣爱好就是演奏钢琴。我们知道一个音乐旋律被表示为长度为 N 的数构成的数列。小Hi在练习过很多曲子以后发现很多作品中的旋律有重复的部分。
我们把一段旋律称为(k,l)-重复的,如果它满足由一个长度为l的字符串重复了k次组成。 如旋律abaabaabaaba是(4,3)重复的,因为它由aba重复4次组成。
小Hi想知道一部作品中k最大的(k,l)-重复旋律。
输入
一行一个仅包含小写字母的字符串。字符串长度不超过 100000。
输出
一行一个整数,表示答案k。
后缀数组 ST表
这题有点带感。
枚举循环节的长度,然后枚举循环节的整数倍位置作为起点,算一下每一段之间的LCP,再花式处理一下边角情况即可。
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 #include<queue> 6 using namespace std; 7 const int mxn=100010; 8 int sa[mxn],rk[mxn],ht[mxn],r[mxn]; 9 int wa[mxn],wb[mxn],cnt[mxn]; 10 inline int cmp(int *r,int a,int b,int l){return r[a]==r[b] && r[a+l]==r[b+l];} 11 void GetSA(int *sa,int n,int m){ 12 int *x=wa,*y=wb; 13 int i,j,p; 14 // for(i=0;i<m;i++)cnt[i]=0; 15 for(i=0;i<n;i++)cnt[x[i]=r[i]]++; 16 for(i=1;i<m;i++)cnt[i]+=cnt[i-1]; 17 for(i=n-1;i>=0;i--)sa[--cnt[x[i]]]=i; 18 for(j=1,p=0;p<n;j<<=1,m=p){ 19 p=0;for(i=n-j;i<n;i++)y[p++]=i; 20 for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j; 21 for(i=0;i<m;i++)cnt[i]=0; 22 for(i=0;i<n;i++)++cnt[x[y[i]]]; 23 for(i=1;i<m;i++)cnt[i]+=cnt[i-1]; 24 for(i=n-1;i>=0;i--)sa[--cnt[x[y[i]]]]=y[i]; 25 swap(x,y); 26 p=1;x[sa[0]]=0; 27 for(i=1;i<n;i++) 28 x[sa[i]]=cmp(y,sa[i],sa[i-1],j)?p-1:p++; 29 } 30 return; 31 } 32 void GetHT(int n){ 33 int i,j,k=0; 34 for(i=1;i<=n;i++)rk[sa[i]]=i; 35 for(i=0;i<n;i++){ 36 if(k)k--; 37 j=sa[rk[i]-1]; 38 while(r[i+k]==r[j+k])k++; 39 ht[rk[i]]=k; 40 } 41 return; 42 } 43 int st[mxn][18],lg[mxn]; 44 int n; 45 void ST_init(){ 46 int i,j;lg[0]=-1; 47 for(i=1;i<=n;i++){ 48 lg[i]=lg[i>>1]+1; 49 st[i][0]=ht[i]; 50 } 51 for(j=1;j<18;j++) 52 for(i=1;i<=n && i+(1<<j)<=n;i++) 53 st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]); 54 return; 55 } 56 int ST(int x,int y){ 57 if(x>y)swap(x,y); ++x; 58 // printf("%d %d ",x,y); 59 int tmp=lg[y-x+1]; 60 return min(st[x][tmp],st[y-(1<<tmp)+1][tmp]); 61 } 62 char s[mxn]; 63 int main(){ 64 int i,j; 65 scanf("%s",s); 66 n=strlen(s); 67 for(i=0;i<n;i++)r[i]=s[i]-'a'+1; 68 GetSA(sa,n+1,27); 69 GetHT(n); 70 ST_init(); 71 int ans=0; 72 for(i=1;i<=n;i++){//枚举间隔长度 73 for(j=0;j+i<n;j+=i){ 74 int x=ST(rk[j],rk[j+i]); 75 ans=max(ans,x/i+1); 76 if(j-i+x%i>0){ 77 x=ST(rk[j-i+x%i],rk[j+x%i]); 78 ans=max(ans,x/i+1); 79 } 80 } 81 } 82 printf("%d\n",ans); 83 return 0; 84 }
后缀自动机二·重复旋律5
描述
小Hi平时的一大兴趣爱好就是演奏钢琴。我们知道一个音乐旋律被表示为一段数构成的数列。
现在小Hi想知道一部作品中出现了多少不同的旋律?
输入
共一行,包含一个由小写字母构成的字符串。字符串长度不超过 1000000。
输出
一行一个整数,表示答案。
后缀自动机模板题
忘了开longlong WA了一串,尴尬
1 /*by SilverN*/ 2 #include<iostream> 3 #include<algorithm> 4 #include<cstring> 5 #include<cstdio> 6 #include<cmath> 7 #include<vector> 8 using namespace std; 9 const int mxn=2000010; 10 int read(){ 11 int x=0,f=1;char ch=getchar(); 12 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 13 while(ch>='0' && ch<='9'){x=x*10+ch-'0';ch=getchar();} 14 return x*f; 15 } 16 struct SAM{ 17 int t[mxn][26],fa[mxn],mx[mxn]; 18 int w[mxn],rk[mxn]; 19 int S,cnt,last; 20 void init(){S=cnt=last=1;return;} 21 void insert(int c){ 22 int np=++cnt,p=last;last=np; 23 mx[np]=mx[p]+1; 24 for(;p && !t[p][c];p=fa[p])t[p][c]=np; 25 if(!p){fa[np]=S;} 26 else{ 27 int q=t[p][c]; 28 if(mx[q]==mx[p]+1)fa[np]=q; 29 else{ 30 int nq=++cnt; 31 mx[nq]=mx[p]+1; 32 memcpy(t[nq],t[q],sizeof t[q]); 33 fa[nq]=fa[q]; 34 fa[q]=fa[np]=nq; 35 for(;p && t[p][c]==q;p=fa[p])t[p][c]=nq; 36 } 37 } 38 return; 39 } 40 void solve(int n){ 41 for(int i=1;i<=cnt;i++)w[mx[i]]++; 42 for(int i=1;i<=n;i++)w[i]+=w[i-1]; 43 for(int i=cnt;i;i--)rk[w[mx[i]]--]=i; 44 long long ans=0; 45 for(int i=cnt;i;i--){ 46 int tmp=rk[i]; 47 ans+=mx[tmp]-mx[fa[tmp]]; 48 } 49 printf("%lld\n",ans); 50 return; 51 } 52 }sa; 53 char s[mxn]; 54 int n,m; 55 int main(){ 56 int i,j; 57 scanf("%s",s+1); 58 n=strlen(s+1); 59 sa.init(); 60 for(i=1;i<=n;i++) 61 sa.insert(s[i]-'a'); 62 sa.solve(n); 63 return 0; 64 }
后缀自动机三·重复旋律6
描述
小Hi平时的一大兴趣爱好就是演奏钢琴。我们知道一个音乐旋律被表示为一段数构成的数列。
现在小Hi想知道一部作品中所有长度为K的旋律中出现次数最多的旋律的出现次数。但是K不是固定的,小Hi想知道对于所有的K的答案。
输入
共一行,包含一个由小写字母构成的字符串S。字符串长度不超过 1000000。
输出
共Length(S)行,每行一个整数,表示答案。
后缀自动机 DP
倒着更新答案
1 #include<iostream> 2 #include<algorithm> 3 #include<cstdio> 4 #include<cstring> 5 using namespace std; 6 const int mxn=2000010; 7 int read(){ 8 int x=0,f=1;char ch=getchar(); 9 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 10 while(ch>='0' && ch<='9'){x=x*10+ch-'0';ch=getchar();} 11 return x*f; 12 } 13 void write(int x){ 14 if(x<0)putchar('-'),x=-x; 15 if(x>9)write(x/10); 16 putchar(x%10+'0'); 17 return; 18 } 19 int f[mxn]; 20 struct SAM{ 21 int t[mxn][26],fa[mxn],mx[mxn],sz[mxn]; 22 int w[mxn],rk[mxn]; 23 int S,cnt,last; 24 void init(){ 25 S=cnt=last=1;return; 26 } 27 void insert(int c){ 28 int np=++cnt,p=last;last=np; 29 mx[np]=mx[p]+1; 30 for(;p && !t[p][c];p=fa[p])t[p][c]=np; 31 if(!p)fa[np]=S; 32 else{ 33 int q=t[p][c]; 34 if(mx[q]==mx[p]+1)fa[np]=q; 35 else{ 36 int nq=++cnt;mx[nq]=mx[p]+1; 37 memcpy(t[nq],t[q],sizeof t[q]); 38 fa[nq]=fa[q]; 39 fa[q]=fa[np]=nq; 40 for(;p && t[p][c]==q;p=fa[p])t[p][c]=nq; 41 } 42 } 43 sz[np]=1; 44 return; 45 } 46 void solve(int n){ 47 for(int i=1;i<=cnt;i++)++w[mx[i]]; 48 for(int i=1;i<=n;i++)w[i]+=w[i-1]; 49 for(int i=cnt;i;i--)rk[w[mx[i]]--]=i; 50 for(int i=cnt;i;i--){ 51 int tmp=rk[i]; 52 sz[fa[tmp]]+=sz[tmp]; 53 f[mx[tmp]]=max(f[mx[tmp]],sz[tmp]); 54 } 55 for(int i=n-1;i;i--)f[i]=max(f[i],f[i+1]); 56 for(int i=1;i<=n;i++){ 57 write(f[i]);puts(""); 58 } 59 return; 60 } 61 }sa; 62 char s[mxn]; 63 int main(){ 64 // freopen("in.txt","r",stdin); 65 int i,j; 66 scanf("%s",s+1); 67 int n=strlen(s+1); 68 sa.init(); 69 for(i=1;i<=n;i++)sa.insert(s[i]-'a'); 70 sa.solve(n); 71 return 0; 72 }
后缀自动机四·重复旋律7
描述
小Hi平时的一大兴趣爱好就是演奏钢琴。我们知道一段音乐旋律可以被表示为一段数构成的数列。
神奇的是小Hi发现了一部名字叫《十进制进行曲大全》的作品集,顾名思义,这部作品集里有许多作品,但是所有的作品有一个共同特征:只用了十个音符,所有的音符都表示成0-9的数字。
现在小Hi想知道这部作品中所有不同的旋律的“和”(也就是把串看成数字,在十进制下的求和,允许有前导0)。答案有可能很大,我们需要对(10^9 + 7)取摸。
输入
第一行,一个整数N,表示有N部作品。
接下来N行,每行包含一个由数字0-9构成的字符串S。
所有字符串长度和不超过 1000000。
输出
共一行,一个整数,表示答案 mod (10^9 + 7)。
后缀自动机 DFS DP
这题挺带感的。
倒着把字符串建成后缀自动机,统计出每个状态结点出现的次数后,从root开始记忆化搜索(DP?)每种转移方式的贡献
1 #include<iostream> 2 #include<algorithm> 3 #include<cstring> 4 #include<cstdio> 5 #include<cmath> 6 #define LL long long 7 using namespace std; 8 const int mod=1e9+7; 9 const int mxn=2000010; 10 LL f[mxn]; 11 struct SAM{ 12 int t[mxn][10],fa[mxn],mx[mxn]; 13 int sz[mxn]; 14 int S,cnt,last; 15 void init(){S=cnt=last=1;return;} 16 void insert(int c){ 17 int np=++cnt,p=last;last=np; 18 mx[np]=mx[p]+1; 19 for(;p && !t[p][c];p=fa[p])t[p][c]=np; 20 if(!p)fa[np]=S; 21 else{ 22 int q=t[p][c]; 23 if(mx[q]==mx[p]+1)fa[np]=q; 24 else{ 25 int nq=++cnt; 26 mx[nq]=mx[p]+1; 27 memcpy(t[nq],t[q],sizeof t[q]); 28 fa[nq]=fa[q]; 29 fa[q]=fa[np]=nq; 30 for(;p && t[p][c]==q;p=fa[p])t[p][c]=nq; 31 } 32 } 33 return; 34 } 35 void solve(int u){ 36 if(f[u]!=-1)return; 37 f[u]=sz[u]=0; 38 for(int i=0;i<10;i++){ 39 if(t[u][i])solve(t[u][i]); 40 else continue; 41 int v=t[u][i]; 42 // printf("u:%d v:%d %lld %d\n",u,v,f[u],f[v]); 43 (f[u]+=f[v]*10%mod+(LL)i*(sz[v]+1))%=mod; 44 sz[u]+=sz[v]+1; 45 if(sz[u]>=mod)sz[u]-=mod; 46 } 47 return; 48 } 49 }sa; 50 int n; 51 char s[mxn]; 52 int main(){ 53 int i,j; 54 scanf("%d",&n); 55 sa.init(); 56 for(i=1;i<=n;i++){ 57 scanf("%s",s+1); 58 int len=strlen(s+1); 59 sa.last=sa.S; 60 for(j=len;j;j--) 61 sa.insert(s[j]-'0'); 62 } 63 memset(f,-1,sizeof f); 64 sa.solve(sa.S); 65 printf("%lld\n",f[1]); 66 return 0; 67 }
后缀自动机五·重复旋律8
描述
小Hi平时的一大兴趣爱好就是演奏钢琴。我们知道一段音乐旋律可以被表示为一段数构成的数列。
小Hi发现旋律可以循环,每次把一段旋律里面最前面一个音换到最后面就成为了原旋律的“循环相似旋律”,还可以对“循环相似旋律”进行相同的变换能继续得到原串的“循环相似旋律”。
小Hi对此产生了浓厚的兴趣,他有若干段旋律,和一部音乐作品。对于每一段旋律,他想知道有多少在音乐作品中的子串(重复便多次计)和该旋律是“循环相似旋律”。
输入
第一行,一个由小写字母构成的字符串S,表示一部音乐作品。字符串S长度不超过100000。
第二行,一个整数N,表示有N段旋律。接下来N行,每行包含一个由小写字母构成的字符串str,表示一段旋律。所有旋律的长度和不超过 100000。
输出
输出共N行,每行一个整数,表示答案。
后缀自动机 脑洞题
妙啊。把str串的[1~len-1]部分复制一遍接到原串后面,比如abcde就处理成abcdeabcd,在后缀自动机上跑一遍就可以匹配所有循环同构串了。
只有匹配长度大于原str串长的状态能计入答案,统计答案的时候要用vis记录已经统计过的结点,防止重复。
有些实现细节不太理解,代码基本靠抄
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 #include<queue> 6 using namespace std; 7 const int mxn=200010; 8 char s[mxn],a[mxn]; 9 int vis[mxn],dtime=0; 10 struct SAM{ 11 int t[mxn][26],fa[mxn],mx[mxn]; 12 int w[mxn],rk[mxn],sz[mxn]; 13 int S,cnt,last; 14 void init(){S=cnt=last=1;return;} 15 void insert(int c){ 16 int np=++cnt,p=last;last=np; 17 mx[np]=mx[p]+1; 18 for(;p && !t[p][c];p=fa[p])t[p][c]=np; 19 if(!p)fa[np]=S; 20 else{ 21 int q=t[p][c]; 22 if(mx[q]==mx[p]+1)fa[np]=q; 23 else{ 24 int nq=++cnt;mx[nq]=mx[p]+1; 25 memcpy(t[nq],t[q],sizeof t[q]); 26 fa[nq]=fa[q]; 27 fa[q]=fa[np]=nq; 28 for(;p && t[p][c]==q;p=fa[p])t[p][c]=nq; 29 } 30 } 31 sz[np]=1; 32 return; 33 } 34 void ST(int n){ 35 int i; 36 for(i=1;i<=cnt;i++)w[mx[i]]++; 37 for(i=1;i<=n;i++)w[i]+=w[i-1]; 38 for(i=cnt;i;i--)rk[w[mx[i]]--]=i;//cnt错打成n WA1 39 for(i=cnt;i;i--){ 40 int tmp=rk[i]; 41 sz[fa[tmp]]+=sz[tmp]; 42 } 43 return; 44 } 45 #define amin(a,b) ((a)<(b)?(a):(b)) 46 void solve(int m){ 47 int now=S,tmp=0,res=0; 48 int ed=m+m-1; 49 for(int i=1,c;i<=ed;i++){ 50 tmp++; 51 c=a[i]-'a'; 52 while(now>1 && !t[now][c])now=fa[now]; 53 tmp=amin(tmp,mx[now]+1); 54 if(t[now][c])now=t[now][c]; 55 while(mx[fa[now]]>=m)now=fa[now]; 56 tmp=amin(tmp,mx[now]); 57 if(tmp>=m){ 58 if(vis[now]!=dtime){ 59 vis[now]=dtime; 60 res+=sz[now]; 61 } 62 } 63 } 64 printf("%d\n",res); 65 return; 66 } 67 }sa; 68 int n; 69 int main(){ 70 int i,j; 71 scanf("%s",s+1); 72 sa.init(); 73 int len=strlen(s+1); 74 for(i=1;i<=len;i++)sa.insert(s[i]-'a'); 75 sa.ST(len); 76 scanf("%d",&n); 77 while(n--){ 78 scanf("%s",a+1); 79 int m=strlen(a+1); 80 for(i=1;i<m;i++)a[m+i]=a[i]; 81 ++dtime; 82 sa.solve(m); 83 } 84 return 0; 85 }
后缀自动机六·重复旋律9
描述
小Hi平时的一大兴趣爱好就是演奏钢琴。我们知道一段音乐旋律可以被表示为一段字符构成的字符串。
现在小Hi已经不满足于单单演奏了!他通过向一位造诣很高的前辈请教,通过几周时间学习了创作钢琴曲的基本理论,并开始对曲目做改编或者原创。两个月后,小Hi决定和前辈进行一场创作曲目的较量!
规则是这样的,有两部已知经典的作品,我们称之为A和B。经典之所以成为经典,必有其经典之处。
刚开始,纸上会有一段A的旋律和一段B的旋律。两人较量的方式是轮流操作,每次操作可以选择在纸上其中一段旋律的末尾添加一个音符,并且要求添加完后的旋律依然是所在作品的旋律(也就是A或B的一个子串)。谁词穷了(无法进行操作)就输了。
小Hi和前辈都足够聪明,但是小Hi还是太年轻,前辈打算教训他一顿。前辈表示会和小Hi进行K次较量,只要小Hi赢了哪怕一次就算小Hi获得最终胜利。但是前提是开始纸上的两段旋律需要他定。小Hi欣然同意,并且表示每次较量都让前辈先操作。
前辈老谋深算,显然是有备而来。他已经洞悉了所有先手必胜的初始(两段)旋律。第i天前辈会挑选字典序第i小的初始(两段)旋律来和小Hi较量。那么问题来了,作为吃瓜群众的你想知道,最后一天即第K天,前辈会定哪两个旋律呢?
初始时两段旋律的字典序比较方式是先比较前一个旋律字典序,一样大则比较后一旋律的字典序。
输入
第一行包含一个正整数,K。K<=10^18
第二行包含一个非空的仅有小写字母构成的字符串,表示作品A。|A|<=10^5
第三行包含一个非空的仅有小写字母构成的字符串,表示作品B。|B|<=10^5
输出
输出共两行,每行一个字符串(可能为空),表示答案。
如果无解则只输出一行"NO"。
后缀自动机 博弈论 sg函数 码农题?
可能算得上是码农题……想明白原理以后就只剩下背板了233
好像有道“讲故事”也是这个套路。
对于单个自动机,可以用sg函数的基本定义(mex)算出每个结点的sg值。
多局面sg游戏,sg异或和不为0的时候先手必胜。
为了优先确保A部分的字典序最小,先在A串的SAM上计算。设当前结点的sg值为nowsg,若加上B串的SAM上所有sg值不为nowsg的状态后总数大于K,就转到B串的自动机上计算,否则继续在A串上贪心转移使得字典序最小……
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 #include<queue> 6 #define LL long long 7 using namespace std; 8 const int mxn=200010; 9 struct SAM{ 10 int t[mxn][26],fa[mxn],sz[mxn],mx[mxn]; 11 int w[mxn],rk[mxn]; 12 LL smm[mxn],ssg[mxn][27]; 13 int sg[mxn];// 14 int S,cnt,last; 15 void init(){S=cnt=last=1;return;} 16 void insert(int c){ 17 int np=++cnt,p=last;last=np; 18 mx[np]=mx[p]+1; 19 for(;p && !t[p][c];p=fa[p])t[p][c]=np; 20 if(!p)fa[np]=S; 21 else{ 22 int q=t[p][c]; 23 if(mx[q]==mx[p]+1)fa[np]=q; 24 else{ 25 int nq=++cnt;mx[nq]=mx[p]+1; 26 memcpy(t[nq],t[q],sizeof t[q]); 27 fa[nq]=fa[q]; 28 fa[q]=fa[np]=nq; 29 for(;p && t[p][c]==q;p=fa[p])t[p][c]=nq; 30 } 31 } 32 sz[np]=1; 33 return; 34 } 35 void TST(int n){ 36 int i; 37 for(i=1;i<=cnt;i++)++w[mx[i]]; 38 for(i=1;i<=n;i++)w[i]+=w[i-1]; 39 for(i=cnt;i;i--)rk[w[mx[i]]--]=i; 40 for(int i=cnt,c;i;i--){ 41 c=rk[i]; 42 sz[fa[c]]+=sz[c]; 43 } 44 return; 45 } 46 void solve(){ 47 bool mex[27]; 48 for(int i=cnt;i;i--){ 49 int tmp=rk[i]; 50 memset(mex,0,sizeof mex); 51 for(int j=0;j<26;j++){ 52 if(!t[tmp][j])continue; 53 int v=t[tmp][j]; 54 mex[sg[v]]=1; 55 for(int k=0;k<27;k++) 56 ssg[tmp][k]+=ssg[v][k]; 57 } 58 sg[tmp]=0; 59 while(mex[sg[tmp]])++sg[tmp]; 60 ++ssg[tmp][sg[tmp]];//以tmp为前缀的状态的sg值之和 61 smm[tmp]=0; 62 for(int j=0;j<27;j++) 63 smm[tmp]+=ssg[tmp][j]; 64 } 65 return; 66 } 67 }sa,sb; 68 char s[mxn],c[mxn]; 69 LL K; 70 void calc(){ 71 int now=sa.S; 72 LL num=0; 73 while(1){//优先处理a 74 bool flag=1; 75 int sgnow=sa.sg[now]; 76 // printf("now:%d sgnow:%d num:%lld cntb:%lld\n", 77 // now,sgnow,num,sb.smm[1]-sb.ssg[1][sgnow]); 78 if(num+sb.smm[1]-sb.ssg[1][sgnow]>=K)break; 79 //如果此时添加b串可以够K个,就不再处理A 80 num+=sb.smm[1]-sb.ssg[1][sgnow]; 81 for(int i=0;i<26;i++){ 82 if(!sa.t[now][i])continue; 83 int v=sa.t[now][i]; 84 LL tmp=0; 85 for(int j=0;j<27;j++) 86 tmp+=(LL)sa.ssg[v][j]*(sb.smm[1]-sb.ssg[1][j]); 87 if(num+tmp<K)num+=tmp; 88 else{ 89 now=v; 90 flag=0; 91 printf("%c",'a'+i); 92 break; 93 } 94 } 95 if(flag){printf("NO\n");return;}//26个字母都凑不够数,无解 96 } 97 printf("\n"); 98 int sgA=sa.sg[now]; 99 now=sb.S; 100 while(num<K){ 101 if(sb.sg[now]!=sgA)num++; 102 if(num==K)break; 103 for(int i=0;i<26;i++){ 104 if(!sb.t[now][i])continue; 105 int v=sb.t[now][i]; 106 LL tmp=sb.smm[v]-sb.ssg[v][sgA]; 107 if(num+tmp<K)num+=tmp; 108 else{ 109 now=v; 110 printf("%c",'a'+i); 111 break; 112 } 113 } 114 } 115 return; 116 } 117 int main(){ 118 int i,j; 119 cin>>K; 120 scanf("%s",s+1); 121 int len1=strlen(s+1); 122 sa.init(); 123 for(i=1;i<=len1;i++)sa.insert(s[i]-'a'); 124 sa.TST(len1); 125 sa.solve(); 126 // 127 scanf("%s",c+1); 128 int len2=strlen(c+1); 129 sb.init(); 130 for(i=1;i<=len2;i++)sb.insert(c[i]-'a'); 131 sb.TST(len2); 132 sb.solve(); 133 // 134 // printf("1:%lld\n",sb.smm[1]); 135 // for(i=0;i<27;i++)printf("%lld ",sb.ssg[1][i]); 136 // puts(""); 137 calc(); 138 return 0; 139 }