統計每個長度 出現的最多次數 後綴自動機 板子

描述

小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;  
} 
發佈了60 篇原創文章 · 獲贊 4 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章