1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cmath> 5 #include <algorithm> 6 #define maxn 500005 7 #define maxm 250005 8 using namespace std; 9 10 int n,tot,root,last,f[maxm],fa[maxn],son[maxn][26],dist[maxn],ri[maxn],sum[maxm],tmp[maxn]; 11 char st[maxm]; 12 struct Tsegment{ 13 void prepare(){tot=root=last=1; memset(dist,0,sizeof(dist)); memset(sum,0,sizeof(sum));} 14 int newnode(int x){ 15 dist[++tot]=x; return tot; 16 } 17 void add(int x){ 18 int p=last,np=newnode(dist[last]+1); last=np; 19 for (;p&&!son[p][x];p=fa[p]) son[p][x]=np; 20 if (p==0) fa[np]=root; 21 else{ 22 int q=son[p][x]; 23 if (dist[p]+1==dist[q]) fa[np]=q; 24 else{ 25 int nq=newnode(dist[p]+1); 26 memcpy(son[nq],son[q],sizeof(son[q])); 27 fa[nq]=fa[q],fa[q]=fa[np]=nq; 28 for (;p&&son[p][x]==q;p=fa[p]) son[p][x]=nq; 29 } 30 } 31 } 32 }SAM; 33 34 int main(){ 35 scanf("%s",st+1),n=strlen(st+1); 36 SAM.prepare(); 37 for (int i=1;i<=n;i++) SAM.add(st[i]-‘a‘); 38 last=root; 39 for (int i=1;i<=n;i++) last=son[last][st[i]-‘a‘],ri[last]=1; 40 memset(sum,0,sizeof(sum)); 41 for (int i=1;i<=tot;i++) sum[dist[i]]++; 42 for (int i=1;i<=n;i++) sum[i]+=sum[i-1]; 43 for (int i=1;i<=tot;i++) tmp[sum[dist[i]]--]=i; 44 memset(f,0,sizeof(f)); 45 for (int i=tot;i>=1;i--){ 46 int x=tmp[i]; 47 if (fa[x]) ri[fa[x]]+=ri[x]; 48 } 49 for (int i=1;i<=tot;i++) f[dist[i]]=max(f[dist[i]],ri[i]); 50 for (int i=n-1;i>=1;i--) f[i]=max(f[i],f[i+1]); 51 for (int i=1;i<=n;i++) printf("%d\n",f[i]); 52 return 0; 53 }
题目链接:http://www.spoj.com/problems/NSUBSTR/
题目大意:给定一个长度为n的字符串,n<=250000,求f[i],i属于[1,n],f[i]表示在给定字符串中长度为i的子串的最多出现次数。
做法:初看此题,我也是一脸懵逼,后来发现出现次数与后缀自动机中的right集合大小有关,这题主要就是如何求right集合的大小,即该点表示的状态的右端点的个数(即出现次数),初始时我们从root开始匹配全串,途径的节点我们设其right为1,其余的节点设为0,某个节点的right就是parent树中以它为根的子树中的right值的和,一次dp即可。注解:parent树是我们记录的fa数组所构成的树,该步骤具体细节见代码。
dist表示SAM上该节点所表示的状态所能代表的最长的字符串长度,我们用right【i】去更新f【dist【i】】即可,最后用一次类似前缀和的算法,求一次后缀最值,显然,长度越小,出现次数一定不会减少。最后输出f数组即可。
后缀自动机。
时间: 2024-10-30 21:40:09