描述
小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;
}