描述
小Hi平時的一大興趣愛好就是演奏鋼琴。我們知道一個音樂旋律被表示爲一段數構成的數列。
現在小Hi想知道一部作品中所有長度爲K的旋律中出現次數最多的旋律的出現次數。但是K不是固定的,小Hi想知道對於所有的K的答案。
解題方法提示
輸入
共一行,包含一個由小寫字母構成的字符串S。字符串長度不超過 1000000。
輸出
共Length(S)行,每行一個整數,表示答案。
樣例輸入
aab
樣例輸出
2
1
1
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstdio>
#include<cstring> // strlen
#include<cmath>
#include<set>
#include<queue>
#include<map>
#include<string>
#include<limits.h> // INT_MAX
#include<functional>
#include<algorithm>
#include<stack>
using namespace std;
const int maxn = 1e6+3;
typedef long long LL;
// SAM: suffix auto machine
// 後綴自動機
int NEXT_FREE_IDX = 0;
int maxlen[2*maxn+10], minlen[2*maxn+10], trans[2*maxn+10][26], slink[2*maxn+10]; //每add一個字符最少增加1個,最多增加兩個狀態
int edpts[2*maxn+10],indegree[2*maxn+10], containPrefix[2*maxn+10];
int new_state( int _maxlen, int _minlen, int* _trans, int _slink){
// 新建一個結點,並進行必要的初始化。
maxlen[NEXT_FREE_IDX] = _maxlen;
minlen[NEXT_FREE_IDX] = _minlen;
for( int i(0); i < 26; i++ ){
if( _trans==NULL )
trans[NEXT_FREE_IDX][i] = -1;
else
trans[NEXT_FREE_IDX][i] = _trans[i];
}
slink[NEXT_FREE_IDX] = _slink;
return NEXT_FREE_IDX++;
}
void add_src(){ // 新建源點
maxlen[0] = minlen[0] = 0; slink[0] = -1;
for( int i(0); i<26; i++ ) trans[0][i] = -1;
NEXT_FREE_IDX = 1;
}
int add_char( char ch, int u ){ // 新插入的字符ch在位置i
int c = ch-'a';
int z = new_state( maxlen[u]+1,-1,NULL,-1); // 新的狀態只包含一個結束位置i
containPrefix[z] = 1;
int v = u;
while( v!=-1 && trans[v][c]==-1 ){
// 對於suffix-link 上所有沒有對應字符ch的轉移
trans[v][c] = z;
v = slink[v]; // 沿着suffix-link往回走
}
if( v==-1 ){
// 最簡單的情況,整條鏈上都沒有對應ch的轉移
minlen[z] = 1; // ch字符自身組成的子串
slink[z] = 0; indegree[0]++;
return z;
}
int x = trans[v][c];
if( maxlen[v]+1 == maxlen[x] ){
// 不用拆分狀態x的情況: 從v到x有對應ch的狀態轉移,但v代表的所有結束位置的後一位置不一定都是ch,故{x代表的結束位置}只是{v代表的結束位置+1}一個子集
// x能代表更廣泛(長度也就可以更長)的字符串,如果滿足maxlen[v]+1 == maxlen[x],則v中的子串+ch就恰好與x中的子串一一對應
// 此時 x 代表的結束位置就是{原來x代表的結束位置,位置i}
minlen[z] = maxlen[x]+1;
slink[z] = x; indegree[x]++;
return z;
}
// 拆分x: x包含一連串連續的子串,將大於maxlen[y]+1的那些(仍然分配到x)和餘下的(分配到y)分別拆分到x和y兩個狀態下
// 那些能夠通過ch轉移到原來的x狀態的所有狀態中,某些要重新指向y,因爲suffix-link和狀態機的性質,很容易實現。
// 同時 y 需要拷貝一份原來x狀態的轉移函數,見new_state();
int y = new_state(maxlen[v]+1, minlen[x]/*-1*/, trans[x], slink[x]);
//slink[y] = slink[x]; // new_state中已賦值
minlen[x] = maxlen[y]+1; // = maxlen[v]+2 ; 拆分後,x包含的最短字符串和y包含的最長字符串需要更新
slink[x] = y; indegree[y]++;
minlen[z] = maxlen[y]+1;
slink[z] = y; indegree[y]++;
int w = v;
while( w!=-1 && trans[w][c]==x ){
trans[w][c] = y;
w = slink[w];
}
//minlen[y] = maxlen[slink[y]]+1; //y的最短不就是原來x的最短了? new_state中賦值
return z;
}
void getEndPtCount(){
queue<int> q;
for( int i(1); i < NEXT_FREE_IDX; i++ )if( !indegree[i] ){
q.push(i);
//edpts[i] = maxlen[i]-minlen[i]; // +1; programming convenient purpose
}
while( !q.empty() ){
int u = q.front(); q.pop();
if( containPrefix[u] ) edpts[u]++;
edpts[ slink[u]] += edpts[u];
if( !--indegree[slink[u]] ) q.push(slink[u]);
}
}
int ans[maxn*2+10];
char str[maxn];
int main(){
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
scanf("%s",str);
int len = strlen(str);
add_src();
int u = 0;
for( int i(0); i < len ; i++ )
u = add_char(str[i], u );
getEndPtCount();
for( int i(0); i < NEXT_FREE_IDX; i++ ){
ans[maxlen[i]] = max( ans[maxlen[i]], edpts[i] );
}
int mm(0);
for( int i(NEXT_FREE_IDX-1); i > 0 ; i-- ){
mm = ans[i] = max( ans[i],mm );
}
for( int i(1); i <= len ; i++ ){
printf("%d\n",ans[i]);
}
return 0;
}