后缀自动机学习笔记3

首先说一下问题描述

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

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

输入

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

输出

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

样例输入

aab
样例输出

2
1
1

状态                                                                   

字符串                                                                                                      

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;
}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值