【SPOJ8222】后缀自动机-重复子串(right数组小应用)
其实算法很容易想到,一个小地方的证明卡了很久orz...
链接:SPOJ8222
问题描述
给定字符串s,定义F(x)表示s的所有长度为x的子串中,重复出现次数最多子串在s中的出现的次数,两次出现可以有部分重叠。
现给定字符串s,求F(1),F(2),...,F(length(s)).
输入格式
一行,即一个字符串s。
输出格式
共length(s)行,每行一个数。第i行为F(i)。
样例输入
ababa
样例输出
3
2
2
1
1
提示
1<=length(s)<=250000
直接建立一个后缀自动机,然后我们利用right数组的定义,在主链(即直接构成原字符串的那条路径)先设定为1,因为这个集合中表示的子串就多一个前缀,之后我们考虑par树倒着全部叠加一遍(我们考虑每个节点的定义,right集合大小为到达该状态的全部所有路径的条数,其倒着加回来,类似于DP的考虑),即这个节点表示的所有子串所在原串中出现的所有次数(right集合大小)。
那么这个实现可以用加边+dfs,可以用基数排序(由于mx全部不超过len,且递增出现),甚至可以愚蠢地像我一样写了个stable_sort()(强行将O(n)搞成了O(n logn) orz过了才发现,不想改了) 然后倒着加回来。
最后再用每个结点上的r[i] 去更新F(mx[i])。但是我卡在这里想了很久,每个状态表示的min到max,为什么只用更新max就够了呢?
因为假如一个状态{abcd,bcd},为r[i],我们可能会去考虑是否要更新{b,c,d},F[3]这个状态。但是abcd出现了r[i]次,那么abc就至少会出现r[i]次,而我们考虑的bcd就一定只出现了r[i],会小于等于abc出现的次数。所以我们不需要考虑更新mx[i]以外的数。
由于本蒟蒻对于后缀自动机也不是特别透彻,思路也略乱,orz orz orz,考虑日后回顾修改。
code:
#include<stdio.h> #include<bits/stdc++.h> using namespace std; const int maxn = 500005; char ss[maxn]; int son[maxn][27],las=1,tot=1,rt=1,par[maxn]; int mx[maxn]; int push(int val) { mx[++tot] = val; return tot; } int r[maxn]; int mmm[maxn]; void extend(int t) { int p,np,q,nq; np = push(mx[las]+1); for(p=las;p&&(!son[p][t]);p=par[p]) son[p][t] = np; if(!p) par[np] = rt; else { q = son[p][t]; if( mx[p]+1==mx[q] ) par[np] = q; else { nq = push( mx[p]+1 ); memcpy(son[nq],son[q],sizeof(son[q])); par[nq] = par[q]; par[q] = par[np] =nq; for(;p&&son[p][t]==q;p=par[p]) son[p][t] = nq; } } las = np; } int f[maxn]; bool cmp1(int a,int b) { return mx[a] < mx[b]; } int main() { scanf("%s",ss+1); int len=strlen(ss+1); for(int i=1;i<=len;i++) extend(ss[i]-'a'); for(int i=1;i<=tot;i++) f[i] = i; stable_sort(f+1,f+1+tot,cmp1); int now = rt; for(int i=1;i<=len;i++) { now = son[now][ss[i]-'a']; r[now]++; } for(int i=tot;i>1;i--) { mmm[mx[f[i]]] = max( mmm[mx[f[i]]] , r[f[i]] ); if(par[f[i]]!=rt) r[par[f[i]]] += r[f[i]]; } for(int i=1;i<=len;i++) { printf("%d\n",mmm[i]); } }