http://www.spoj.com/problems/NSUBSTR/ (题目链接)
题意
给出一个字符串S,令${F(x)}$表示S的所有长度为x的子串出现次数的最大值。求${F(1)......F(length(S))}$
Solution
后缀自动机例题,下面写几点自己认为理解后缀自动机的重点。
- 后缀自动机相对于后缀树就是将Right集合相同的子串合用一个节点来表示。每一个节点代表一个状态S,这个状态可能包含很多长度区间连续的子串,这些子串的右端点固定,它们的Right集合相同。
- 往上跳parent的过程相当于将子串的前面一节截掉,得到一个长度更短的子串,它们的Right集合变多了。走状态转移边的过程相当于在子串的后面添加新的字符,到达新的状态。
- 构造过程中,情况一很好理解,考虑情况二和情况三的区别。情况二是parent中存在x边到达某一个状态${V_q}$,并且这个状态的Right集合可以直接加入当前这个Right。情况三是虽然存在状态${V_q}$,但是这个状态所包含的子串有一部分长度比较长的无法包含要加入的这个Right,所以将它拆成两份。
对于这道题,我们需要做的就是计算SAM中每个节点的Right集合的大小,即在串中的出现次数。因为parent树的某个节点Right集合是它父亲的真子集,所以我们考虑从parent树的底端向上不断更新祖先的Right集合。
代码模着hzwer写的,加了点注释。
代码
// spoj8222 #include<algorithm> #include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<cmath> #include<ctime> #define LL long long #define inf 1<<30 #define Pi acos(-1.0) #define free(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout); using namespace std; const int maxn=500010; int n,last,Dargen,cnt; int ch[maxn][26],a[maxn],b[maxn],t[maxn],f[maxn],l[maxn],r[maxn],fa[maxn]; char s[maxn]; void add(int x) { int c=a[x]; int p=last,np=++cnt;last=np; //p是上次插入的节点,np为现在正在插入的这个节点,last变成了np l[np]=x; //Max(s),也就是到达这个节点的最长的子串(相当于x的前缀的长度→_→) for (;p && !ch[p][c];p=fa[p]) ch[p][c]=np; //p以及其在parent树上的祖先没有c边的全部连边(可以从right集合的角度去考虑) if (!p) fa[np]=Dargen; //如果都没有c边,则np的parent为dargen————情况1 else { int q=ch[p][c]; //找到了深度最深的有c边的祖先 if (l[q]==l[p]+1) fa[np]=q; //情况2 else { //情况3 int nq=++cnt;l[nq]=l[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q]; fa[np]=fa[q]=nq; for (;ch[p][c]==q;p=fa[p]) ch[p][c]=nq; } } } int main() { scanf("%s",s+1); int len=strlen(s+1); for (int i=1;i<=len;i++) a[i]=s[i]-‘a‘; last=Dargen=++cnt; for (int i=1;i<=len;i++) add(i); for (int p=Dargen,i=1;i<=len;i++) p=ch[p][a[i]],r[p]++; //先将主链上的right全部加1 for (int i=1;i<=cnt;i++) b[l[i]]++; //按照l[x]从小到大基数排序 for (int i=1;i<=len;i++) b[i]+=b[i-1]; for (int i=1;i<=cnt;i++) t[b[l[i]]--]=i; for (int i=cnt;i>=1;i--) r[fa[t[i]]]+=r[t[i]]; //从后往前for,更新parent的right大小 for (int i=1;i<=cnt;i++) f[l[i]]=max(f[l[i]],r[i]); //更新答案 for (int i=len;i>=1;i--) f[i]=max(f[i],f[i+1]); for (int i=1;i<=len;i++) printf("%d\n",f[i]); return 0; }
时间: 2024-10-01 06:36:08