后缀自动机学习笔记2(hiho128周)

本文详细介绍了后缀自动机(SAM)的构建原理及其实现步骤,包括如何通过O(length(S))的时间复杂度构建SAM,针对不同情况的转换函数实现方法,并提供了一段完整的示例代码用于解决寻找不同子串数量的问题。
摘要由CSDN通过智能技术生成

后缀自动机最出名的应该是其时空复杂度均为O(length(S)),想要实现O(length(S))的构造,我们对于每个状态不能保存太多数据。例如substring(st)肯定是没法保存下来了。对于状态st我们只保存如下数据:

maxlen[st]:st包含的最长子串的长度
minlen[st]:st包含的最短字串的长度
trans[st][c]:st的转移函数,c为新的字符
slink[st]:st的Suffix Link


suffix-path(u->S)是S[1..i], S[2..i], S[3..i], ... , S[i], ""(空串)对应的状态们恰好就是从u到初始状态S的由Suffix Link连接起来路径上的所有状态(S[1..i]对应的状态是u)。例如suffix-path(7->S)={7,8,5,S},suffix-path(9->S)={9,S}。


下一步说下转化函数怎么实现的,主要分为三种情况分析

(一)

S[1..i+1]这个子串不能被以前的SAM识别,所以我们至少需要添加一个状态z,z至少包含S[1..i+1]这个子串。例如下面的S[1..i]="aa",那么S[1..i]="aab",添加了一个新的状态3。

最简单的情况:对于suffix-path(u->S)的任意状态v,都有trans[v][S[i+1]]=NULL。这时我们只要令trans[v][S[i+1]]=z,并且令slink[st]=S即可

这里trans[v][i+1]=NULL的意思是,例如对下面的情况中suffix-path(u->S)的状态都有2,1,S,对于状态2包含的字符串有{aa},trans[2][“b”]得到的字符串为{aab}在之前的字符串中没有出现过所以trans[2][b]=NULL,同理trans[1][“b”]=NULL,trans[S][“b”]=NULL,这是只需要使trans[2][“b”]=trans[1][“b”]=trans[S][“b”]=3(图中红色实线),同时slink[st]=S(图中红色虚线)。



对于有trans[v][S[i+1]]!=NULL的情况,如下图,当添加新的字符"a"的时候,对于状态S有trans[S]["a"]={a},已经在状态1中出现。对于这种情况分两种讨论。

(二)

可以认为在suffix-path(u->S)遇到的第一个状态v满足trans[v][S[i+1]]=x。这时我们需要讨论x包含的子串的情况。

第一种:如果x中包含的最长子串就是v中包含的最长子串接上字符S[i+1],等价于maxlen(v)+1=maxlen(x),比如在下面的例子里,v=S, x=1,longest(v)是空串,longest(1)="a"就是longest(v)+'a'。这种情况比较简单,我们只要增加slink[z]=x(图中红色虚线)即可。




(三)

第二种:如果x中包含的最长子串 不是 v中包含的最长子串接上字符S[i+1],等价于maxlen(v)+1 < maxlen(x)

例如下面这种情况:对于状态3存在trans[S][“b”]=3(即trans[S][b]中的字符串为"b"存在于substrings(3)中),且longest(3)="aab",longest(S)+'b'="b",两者不相等。这种情况需要从x拆分出新的状态y,并且把原来x中长度小于等于longest(v)+c的子串分给y,其余字串留给x。同时令trans[v..w][c]=y,slink[y]=slink[x], slink[x]=slink[z]=y。例如下图新增加了一个状态5,同时将原来指向状态3中长度小于等于longest(S)+"b"的子串“b”指向状态5,同时令trans[S]["b"]=5,slink[5]=slink[3]=S, slink[3]=slink[4]=5。




实例

问题

小Hi平时的一大兴趣爱好就是演奏钢琴。我们知道一个音乐旋律被表示为一段数构成的数列。
现在小Hi想知道一部作品中出现了多少不同的旋律?
解题方法提示
输入
共一行,包含一个由小写字母构成的字符串。字符串长度不超过 1000000。
输出
一行一个整数,表示答案。
样例输入

aab
样例输出
5
代码

//
//  main.cpp
//  hiho128
//
//  Created by 小哲 on 16/12/13.
//  Copyright © 2016年 小哲. All rights reserved.
//

#include <iostream>
#include<string>
#include <string.h>
using namespace std;

const int MAXL = 1000000;
int n = 0;
int maxlen[2 * MAXL], minlen[2 * MAXL], trans[2 * MAXL][26], slink[2 * MAXL];//长度为2 * MAXL是因为这里的每添加一个新的字符最多增加两个状态(一个状态z,一个中间状态y)

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;
    return n++;
}

int add_char(char ch, int u) {
    int c = ch - 'a';
    int z = new_state(maxlen[u] + 1, -1, NULL, -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;
}

int main(int argc, const char * argv[]) {
    
    int nextI=new_state(0, -1, NULL, -1);//初始化状态S
    string s;
    cin>>s;
    unsigned long l=s.length();
    for (int i=0; i<s.length(); i++) {
        nextI= add_char(s[i],nextI);
    }
   unsigned long  count =0;//最终结果可能是平方级别的,所以用int型可能会溢出(C++有符号int型超出后会循环,不会报错,但是结果不对)
    for (int i=1; i<n; i++) {
		if(maxlen[i]>0&&minlen[i]>0)
		{
			count=count+maxlen[i]-minlen[i]+1;
		}
    }
    cout<<count<<endl;
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值