@spoj - [email protected] Substrings
阿新 • • 發佈:2019-01-10
目錄
@[email protected]
給定一個僅包含小寫字母的字串 S,對於每一個 i 滿足 1 <= i <= |S|,求長度為 i 的,在 S 中出現次數最多的串出現了多少次?
input
輸入一個長度小於等於 250000 的,僅包含小寫字母的串。
output
輸出 |S| 行,第 i 行表示長度為 i 的在 S 中出現次數最多的串的出現次數。
sample input
ababa
sample output
3
2
2
1
1
@[email protected]
想想我們在後綴自動機中 end-pos 的含義:每一次出現的結束位置的集合。
我們要求解它出現了多少次,即要求解 |end-pos|。
再想想我們構建字尾自動機採用的是增量法。
每次加入一個字元,就會多出來一個以前從未出現過的一個新結束位置。
然後想想我們 fa 的含義,一個結點的 end-pos 必然是 fa 的 end-pos 的子集。
所以假如多出來一個新的結束位置,那麼它會影響它所有的祖先。
最後,一個結點的最長子串長度必然大於它 fa 的最長子串,因此我們可以按照最長子串長度進行桶排序,再從後往前掃一邊,更新父親的值,就可以求解出每一個結點的出現次數。
假如某一個結點出現次數為 k,那麼它的最長子串的所有後綴出現次數也一定大於等於 k,所以我們可以直接用 k 去更新最長子串長度的值,再從後往前用後一個去更新前一個。
@accepted [email protected]
#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]); }
@[email protected]
我們的 end-pos 大小並不等於子樹大小,而是等於子樹內非複製的(即每一次用增量法加入的)點的個數。