【spoj8222】Substrings

 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 }
View Code

题目链接: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数组即可。

后缀自动机。

posted @ 2016-06-01 15:18  oyzx~  阅读(197)  评论(0编辑  收藏  举报