HihoCoder1449 后缀自动机三·重复旋律6
描述
小Hi平时的一大兴趣爱好就是演奏钢琴。我们知道一个音乐旋律被表示为一段数构成的数列。
现在小Hi想知道一部作品中所有长度为K的旋律中出现次数最多的旋律的出现次数。但是K不是固定的,小Hi想知道对于所有的K的答案。
输入
共一行,包含一个由小写字母构成的字符串S。字符串长度不超过 1000000。
输出
共Length(S)行,每行一个整数,表示答案。
题解:
对S建立一个后缀自动机,我们可以先求出每个状态在原串中出现的次数cnt。对于复制的结点令cnt=0,转移的结点cnt=1,因为转移结点的状态保证至少有一个字符串,然后我们应该将一个状态的cnt值添加到它后缀链接的状态上,因为明显转移的状态的字符串被包含在原状态字符串中,所以我们可以用拓扑排序来更新状态的cnt值。
我们最后要求的是不同长度的字符串中出现次数的最大值,假设为ans[i],i从len(s)到1,ans肯定是非递减的,因为当前状态的字符串长于下一个状态的字符串,这意味着当前状态的字符串包含一个与下一个状态字符串等长的后缀。
所以有ans[i] = max(ans[i],ans[i+1]),ans的初始化为ans[st[i].len] = max(ans[st[i].len],st[i].cnt);
参考代码:
#include<bits/stdc++.h> using namespace std; #define PI acos(-1.0) #define mkp make_pair #define pii pair<int,int> #define fi first #define se second #define pb push_back typedef long long ll; const int INF=0x3f3f3f3f; const int maxn=1e6+10; char s[maxn]; struct SAM{ int fa[maxn<<1],l[maxn<<1],nxt[maxn<<1][26],last,cnt; int ans[maxn],num[maxn<<1],c[maxn<<1],rk[maxn<<1]; void Init() { memset(nxt[1],0,sizeof(nxt[1])); memset(num,0,sizeof(num)); memset(ans,0,sizeof(ans)); memset(c,0,sizeof(c)); memset(rk,0,sizeof(rk)); last=cnt=1; fa[1]=0;l[1]=0; } int NewNode() { ++cnt; memset(nxt[cnt],0,sizeof(nxt[cnt])); fa[cnt]=l[cnt]=0; return cnt; } void Add(int c) { int p=last,np=NewNode();num[np]=1; last=np;l[np]=l[p]+1; while(p&&!nxt[p][c]) nxt[p][c]=np,p=fa[p]; if(!p) fa[np]=1; else { int q=nxt[p][c]; if(l[q]==l[p]+1) fa[np]=q; else { int nq=NewNode(); memcpy(nxt[nq],nxt[q],sizeof(nxt[q])); fa[nq]=fa[q]; l[nq]=l[p]+1; fa[q]=fa[np]=nq; while(nxt[p][c]==q) nxt[p][c]=nq,p=fa[p]; } } } void topsort() { for(int i=1;i<=cnt;i++) c[l[i]]++; for(int i=1;i<=cnt;i++) c[i]+=c[i-1]; for(int i=1;i<=cnt;i++) rk[c[l[i]]--]=i; for(int i=cnt;i>=1;i--) { int x=rk[i]; num[fa[x]]+=num[x]; ans[l[x]] = max(ans[l[x]],num[x]); } for(int i=1,len=strlen(s+1);i<=len;i++) printf("%d\n",ans[i]); } } sam; int main() { scanf("%s",s+1); sam.Init(); int len=strlen(s+1); for(int i=1;i<=len;++i) sam.Add(s[i]-'a'); sam.topsort(); return 0; }