标签:strong last efi length ref 题目 lib cst 后缀自动机
http://www.spoj.com/problems/NSUBSTR/ (题目链接)
给出一个字符串S,令${F(x)}$表示S的所有长度为x的子串出现次数的最大值。求${F(1)......F(length(S))}$
后缀自动机例题,下面写几点自己认为理解后缀自动机的重点。
对于这道题,我们需要做的就是计算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;
}
标签:strong last efi length ref 题目 lib cst 后缀自动机
原文地址:http://www.cnblogs.com/MashiroSky/p/6287433.html