后缀自动机学习笔记3

来源:互联网 发布:中国人工智能会议2017 编辑:程序博客网 时间:2024/06/05 06:44

首先说一下问题描述

Hi平时的一大兴趣爱好就是演奏钢琴。我们知道一个音乐旋律被表示为一段数构成的数列。

现在小Hi想知道一部作品中所有长度为K的旋律中出现次数最多的旋律的出现次数。但是K不是固定的,小Hi想知道对于所有的K的答案。

输入

共一行,包含一个由小写字母构成的字符串S。字符串长度不超过 1000000

输出

Length(S)行,每行一个整数,表示答案。

样例输入

aab
样例输出

211

状态                                                                   

字符串                                                                                                      

endpos                                                                      

S

空串

{0,1,2,3,4,5,6}

1

a

{1,2,5}

2

aa

{2}

3

aab

{3}

4

aabb,abb,bb

{4}

5

b

{3,4,6}

6

aabba,abba,bba,ba

{5}

7

aabbab,abbab,bbab,bab

{6}

8

ab

{3,6}

9

aabbabd,abbabd,bbabd,babd,abd,bd,d

{7}

还是按“aabbabd”做例子,可以发现a出现的次数为|endpos(1)|,这里所需要做的就是求|endpos(st)| 的大小
这里对于每一个状态发现endpos(8)=endpos(3)Uendpos(7),|endpos(8)|=|endpos(3)|+|endpos(7)|ps:slink指向(即绿线指向)
对于主干上的状态(1,2,3,6,7,9)需要加1,例如:|endpos(1)|=|endpos(2)|+|endpos(6)|+1;
解题思路

先构建SAM,顺便将主干点标示出来,然后对slink构成的树自底向上用拓扑排序构件出来|endpos(st)| 

值得注意的是ans[1], ans[2], ... ans[length(S)]一定是一个单调递减序列。所以我们对于每个状态st,只需要更新ans[maxlen(st)]。之后令i = length(S)-1 .. 1,从后向前扫描一遍,令ans[i] = max(ans[i], ans[i+1]),即可。

代码:

////  main.cpp//  hiho129////  Created by 小哲 on 16/12/17.//  Copyright © 2016年 小哲. All rights reserved.//#include <iostream>#include <string.h>#include <queue>using namespace std;const int MAXL = 1000000;string s;int n = 0;int maxlen[2 * MAXL + 10], minlen[2 * MAXL + 10], trans[2 * MAXL + 10][26], slink[2 * MAXL + 10],lv[2*MAXL],ans[MAXL],endpos[2*MAXL];int du[2*MAXL];int new_state(int _maxlen, int _minlen, int* _trans, int _slink) {    maxlen[n] = _maxlen;    minlen[n] = _minlen;    for(int i = 0; i < 26; i++) {        if(_trans == NULL)            trans[n][i] = -1;        else            trans[n][i] = _trans[i];    }    slink[n] = _slink;    lv[n]=0;    endpos[n]=0;    du[n]=0;    return n++;}int add_char(char ch, int u) {    int c = ch - 'a';    int z = new_state(maxlen[u] + 1, -1, NULL, -1);    lv[z]=1;    int v = u;    while(v != -1 && trans[v][c] == -1) {        trans[v][c] = z;        v = slink[v];    }    if(v == -1) { //最简单的情况,suffix-path(u->S)上都没有对应字符ch的转移        minlen[z] = 1;        slink[z] = 0;        return z;    }    int x = trans[v][c];    if(maxlen[v] + 1 == maxlen[x]) { //较简单的情况,不用拆分x        minlen[z] = maxlen[x] + 1;        slink[z] = x;        return z;    }    int y = new_state(maxlen[v] + 1, -1, trans[x], slink[x]); //最复杂的情况,拆分x    slink[y] = slink[x];    minlen[x] = maxlen[y] + 1;    slink[x] = y;    minlen[z] = maxlen[y] + 1;    slink[z] = y;    int w = v;    while(w != -1 && trans[w][c] == x) {        trans[w][c] = y;        w = slink[w];    }    minlen[y] = maxlen[slink[y]] + 1;    return z;}void getEndpos()//  拓扑排序{    for (int i=1;i<n ; i++) {        du[slink[i]]++;    }    queue<int> qu;    for (int i=1; i<n; i++) {        if (!du[i]) {            qu.push(i);        }    }    while (!qu.empty()) {        int v = qu.front();      // 从队列中取出一个顶点        qu.pop();        if (lv[v]) {            endpos[v]++;        }        endpos[slink[v]]+=endpos[v];        du[slink[v]]--;        if (!du[slink[v]]) {            qu.push(slink[v]);        }    }}void getAns(){//扫描获取值    for (int i=0; i<=s.length(); i++) {        ans[i]=0;    }    for (int i=0; i<n; i++) {        ans[maxlen[i]]=max(ans[maxlen[i]], endpos[i]);    }    for (int i=s.length()-1; i>0; i--) {        ans[i]=max(ans[i],ans[i+1]);    }}int main(int argc, const char * argv[]) {    int nextI=new_state(0, -1, NULL, -1);;    cin>>s;    for (int i=0; i<s.length(); i++) {        nextI= add_char(s[i],nextI);    }    getEndpos();    getAns();    for (int i=1; i<=s.length(); i++) {        cout<<ans[i]<<endl;    }        return 0;}






1 0
原创粉丝点击