统计每个长度 出现的最多次数 后缀自动机 板子

描述

小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;  
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值