@spoj - nsubstr@ Substrings
@description@
给定一个仅包含小写字母的字符串 S,对于每一个 i 满足 1 <= i <= |S|,求长度为 i 的,在 S 中出现次数最多的串出现了多少次?
input
输入一个长度小于等于 250000 的,仅包含小写字母的串。
output
输出 |S| 行,第 i 行表示长度为 i 的在 S 中出现次数最多的串的出现次数。
sample input
ababa
sample output
3
2
2
1
1
@solution@
想想我们在后缀自动机中 end-pos 的含义:每一次出现的结束位置的集合。
我们要求解它出现了多少次,即要求解 |end-pos|。
再想想我们构建后缀自动机采用的是增量法。
每次加入一个字符,就会多出来一个以前从未出现过的一个新结束位置。
然后想想我们 fa 的含义,一个结点的 end-pos 必然是 fa 的 end-pos 的子集。
所以假如多出来一个新的结束位置,那么它会影响它所有的祖先。
最后,一个结点的最长子串长度必然大于它 fa 的最长子串,因此我们可以按照最长子串长度进行桶排序,再从后往前扫一边,更新父亲的值,就可以求解出每一个结点的出现次数。
假如某一个结点出现次数为 k,那么它的最长子串的所有后缀出现次数也一定大于等于 k,所以我们可以直接用 k 去更新最长子串长度的值,再从后往前用后一个去更新前一个。
@accepted code@
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 250000;
struct sam{
sam *ch[26], *fa; int mx;
sam *nxt; int siz;
}pl[2*MAXN + 5], *bin[MAXN + 5], *tcnt, *root, *lst;
void init() {
tcnt = root = &pl[0];
for(int i=0;i<26;i++)
root->ch[i] = NULL;
root->fa = NULL, root->mx = 0;
}
void add_bin(sam *x) {
x->nxt = bin[x->mx];
bin[x->mx] = x;
}
sam *newnode() {
tcnt++;
for(int i=0;i<26;i++)
tcnt->ch[i] = NULL;
tcnt->fa = NULL, tcnt->mx = 0;
return tcnt;
}
void sam_extend(int x) {
sam *cur = newnode(), *p = lst;
cur->mx = lst->mx + 1, cur->siz = 1, lst = cur;
add_bin(cur);
while( p && !p->ch[x] )
p->ch[x] = cur, p = p->fa;
if( !p )
cur->fa = root;
else {
sam *q = p->ch[x];
if( q->mx == p->mx + 1 )
cur->fa = q;
else {
sam *cne = newnode();
(*cne) = (*q), cne->mx = p->mx + 1, cne->siz = 0;
add_bin(cne);
q->fa = cur->fa = cne;
while( p && p->ch[x] == q )
p->ch[x] = cne, p = p->fa;
}
}
}
char s[MAXN + 5]; int ans[MAXN + 5];
int main() {
init(); lst = root;
scanf("%s", s); int lens = strlen(s);
for(int i=0;i<lens;i++)
sam_extend(s[i] - 'a');
for(int i=lens;i>=1;i--) {
while( bin[i] ) {
bin[i]->fa->siz += bin[i]->siz;
ans[i] = max(ans[i], bin[i]->siz);
bin[i] = bin[i]->nxt;
}
}
for(int i=1;i<=lens;i++)
printf("%d\n", ans[i]);
}
@details@
我们的 end-pos 大小并不等于子树大小,而是等于子树内非复制的(即每一次用增量法加入的)点的个数。