【后缀自动机】hihocoder1445 后缀自动机二·重复旋律5
解题方法提示
小Hi:本周的题目其实就是给定一个字符串S,要求出S的所有不同子串的数目。小Ho你知道如何快速求解么?
小Ho:我们最近在讨论后缀自动机,所以肯定是和后缀自动机有关!根据上周学习的SAM的基本概念和性质,SAM的每个状态st都包含了一部分S的子串,记作substrings(st),并且(1)对于两个不同状态u和v,包含的子串substrings(u) ∩ substrings(v) = ∅; (2)每个子串都恰好被一个状态包含。所以我们只要构造出来S对应的SAM,再对所有状态st求Σ(maxlen(st)-minlen(st))就是子串的数目。
小Hi:没错。上周我们提到SAM有O(length(S))的构造法。这周我们就来讲一讲如何构造。
小Hi:首先,为了实现O(length(S))的构造,我们对于每个状态不能保存太多数据。例如substring(st)肯定是没法保存下来了。对于状态st我们只保存如下数据:
数据 | 含义 |
---|---|
maxlen[st] | st包含的最长子串的长度 |
minlen[st] | st包含的最短字串的长度 |
trans[st][] | st的转移函数 |
slink[st] | st的Suffix Link |
小Hi:其次,我们用增量法构造S对应的SAM。我们从初始状态开始,每次添加一个字符S[1], S[2], ... S[N],依次构造可以识别S[1], S[1..2], S[1..3], ... S[1..N]=S的SAM。
小Hi:假设我们已经构造好了S[1..i]的SAM。这时我们要添加字符S[i+1],于是我们新增了i+1个S[i+1]的后缀要识别:S[1..i+1], S[2..i+1], ... S[i..i+1], S[i+1]。 考虑到这些新增状态分别是从S[1..i], S[2..i], S[3..i], ... , S[i], ""(空串)通过字符S[i+1]转移过来的,所以我们还要对S[1..i], S[2..i], S[3..i], ... , S[i], ""(空串)对应的状态们增加相应的转移。
小Hi:我们假设S[1..i]对应的状态是u,等价于S[1..i]∈ substrings(u)。根据上周的讨论我们知道S[1..i], S[2..i], S[3..i], ... , S[i], ""(空串)对应的状态们恰好就是从u到初始状态S的由Suffix Link连接起来路径上的所有状态,不妨称这条路径(上所有状态集合)是suffix-path(u->S)。
小Hi:显然至少S[1..i+1]这个子串不能被以前的SAM识别,所以我们至少需要添加一个状态z,z至少包含S[1..i+1]这个子串。
小Hi:首先考虑一种最简单的情况:对于suffix-path(u->S)的任意状态v,都有trans[v][S[i+1]]=NULL。这时我们只要令trans[v][S[i+1]]=z,并且令slink[st]=S即可。
小Hi:例如我们已经得到"aa"的SAM,现在希望构造"aab"的SAM。如下图所示:
小Hi:此时u=2,z=3,suffix-path(u->S)是桔色状态组成的路径2-1-S。并且这3个状态都没有对应字符b的转移。所以我们只要添加红色转移trans[2][b]=trans[1][b]=trans[S][b]=z即可。当然也不要忘了slink[3]=S。
小Ho:那要是suffix-path(u->S)上有一个节点v,使得trans[v][S[i+1]]!=NULL怎么办?
小Hi:好问题。我们以下图为例,假设我们已经构造"aabb"的SAM如图,现在我们要增加一个字符a构造"aabba"的SAM。
小Hi:这时u=4,z=6,suffix-path(u->S)是桔色状态组成的路径4-5-S。对于状态4和状态5,由于它们都没有对应字符a的转移,所以我们只要添加红色转移trans[4][a]=trans[5][a]=z=6即可。面对S时我们遇到了小Ho你提出的问题,trans[S][a]=1已经存在,怎么办?
小Ho:怎么办呢?
小Hi:不失一般性,我们可以认为在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即可。
小Hi:如果x中包含的最长子串 不是 v中包含的最长子串接上字符S[i+1],等价于maxlen(v)+1 < maxlen(x),这种情况最为复杂,不失一般性,我们用下图表示这种情况,这时增加的字符是c,状态是z。
小Hi:在suffix-path(u->S)这条路径上,从u开始有一部分连续的状态满足trans[u..][c]=NULL,对于这部分状态我们只需增加trans[u..][c]=z。紧接着有一部分连续的状态v..w满足trans[v..w][c]=x,并且longest(v)+c不等于longest(x)。这时我们需要从x拆分出新的状态y,并且把原来x中长度小于等于longest(v)+c的子串分给y,其余字串留给x。同时令trans[v..w][c]=y,slink[y]=slink[x], slink[x]=slink[z]=y。
小Ho:好像比较复杂。
小Hi:我们来举个例子。假设我们已经构造"aab"的SAM如图,现在我们要增加一个字符b构造"aabb"的SAM。
小Hi:当我们处理在suffix-path(u->S)上的状态S时,遇到trans[S][b]=3。并且longest(3)="aab",longest(S)+'b'="b",两者不相等。其实不相等意味增加了新字符后endpos("aab")已经不等于endpos("b"),势必这两个子串不能同属一个状态3。这时我们就要从3中新拆分出一个状态5,把"b"及其后缀分给5,其余的子串留给3。同时令trans[S][c]=5, slink[5]=slink[3]=S, slink[3]=slink[6]=5。
小Hi:整个过程的代码如下,其中状态0代表初始状态S;状态u, v, x, y, z的意义如上文所述;-1代表slink或者trans不存在。
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
typedef long long ll;
#define MAXL 1000000
#define MAXC 26
char s[MAXL+10];//文本串
int len/*文本串长度*/;
struct SAM{
int n/*状态数0~n-1*/,maxlen[2*MAXL+10],minlen[2*MAXL+10],trans[2*MAXL+10][MAXC],slink[2*MAXL+10];
int new_state(int _maxlen,int _minlen,int _trans[],int _slink){
maxlen[n]=_maxlen;
minlen[n]=_minlen;
for(int i=0;i<MAXC;++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 pos){
if(u==-1){
return new_state(0,0,NULL,-1);
}
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;
}
}sam;
int m;
ll ans;
int main(){
// freopen("hihocoder1445.in","r",stdin);
// freopen("hihocoder1445.out","w",stdout);
char T[MAXL+10];
scanf("%s",s);
len=strlen(s);
int U=sam.add_char(0,-1,0);
for(int i=0;i<len;++i){
U=sam.add_char(s[i],U,i);
}
for(int i=1;i<sam.n;++i){
ans+=(ll)(sam.maxlen[i]-sam.minlen[i]+1);
}
cout<<ans<<endl;
return 0;
}
【后缀自动机】hihocoder1445 后缀自动机二·重复旋律5的更多相关文章
- hihoCoder #1445 : 后缀自动机二·重复旋律5
#1445 : 后缀自动机二·重复旋律5 时间限制:10000ms 单点时限:2000ms 内存限制:256MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为一段数构成的数 ...
- hihoCoder_1445_后缀自动机二·重复旋律5
#1445 : 后缀自动机二·重复旋律5 时间限制:10000ms 单点时限:2000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为一段数构成的数 ...
- hiho一下第128周 后缀自动机二·重复旋律5
#1445 : 后缀自动机二·重复旋律5 时间限制:10000ms 单点时限:2000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为一段数构成的数 ...
- hiho一下121周 后缀数组二·重复旋律2
后缀数组二·重复旋律2 时间限制:5000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为长度为 N 的数构成的数列.小Hi ...
- hihocoder #1407 : 后缀数组二·重复旋律2
#1407 : 后缀数组二·重复旋律2 Time Limit:5000ms Case Time Limit:1000ms Memory Limit:256MB 描述 小Hi平时的一大兴趣爱好就是演奏钢 ...
- hiho一下第131周 后缀自动机二·重复旋律8(循环相似子串)
后缀自动机五·重复旋律8 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成的数列. 小Hi ...
- hiho一下第130周 后缀自动机二·重复旋律7
后缀自动机四·重复旋律7 时间限制:15000ms 单点时限:3000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成的数列. 神奇的 ...
- hiho一下第129周 后缀自动机二·重复旋律6
后缀自动机三·重复旋律6 时间限制:15000ms 单点时限:3000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为一段数构成的数列. 现在小Hi ...
- HihoCoder1407 后缀数组二·重复旋律2
重复旋律2 时间限制:5000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为长度为 N 的数构成的数列.小Hi在练习过很多 ...
随机推荐
- C# 关于调用微信接口的代码
调用微信接口前需要准备的内容. 1.微信公众平台的appid 2.微信公众平台的secret 3..获取tokenid 4.获取ticket 5.生成签名的随机串 6.生成签名的时间戳 7.生成签名 ...
- hdu 1213 How Many Tables(并查集算法)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1213 How Many Tables Time Limit: 2000/1000 MS (Java/O ...
- c语言中网络字节序和主机字节序的转换
函数说明 相关函数:htonl, htons, ntohl 头文件:#include <netinet/in.h> 定义函数:unsigned short int ntohs(unsi ...
- exit()与_exit()区别
exit()与_exit()都是用来终止进程的函数,当程序执行到两者函数时,系统将会无条件停止剩下操作,清除进程结构体相应信息,并终止进程运行. 二者的主要区别在于:exit()函数在执行时,系统会检 ...
- 用tkinter实现的gui小工具
import tkinter import requests import json from tkinter import * class FindLocation(object): def __i ...
- nginx源码分析--使用GDB调试(strace、 pstack )
nginx源码分析--使用GDB调试(strace. pstack ) http://blog.csdn.net/scdxmoe/article/details/49070577
- 自动安装jar包到本地仓库
参考博客:http://blog.csdn.net/m0_37797991/article/details/73394873
- [How to]简单易用的拷贝Mac文件路径方法
效果: 在你想拷贝路径的文件夹或者文件上右键会出现 copy path 选项! 实现: 1.打开finder的的Automator组件 2.选择[服务]选项,点击[选取]按钮 3.搜索操作项目中[拷贝 ...
- 其实linux下远程windows并不麻烦
1:如果你是安装的ubuntu.那么安装完成之后就自带一个Remmina的远程桌面工具 2:这里我们需要安装rdesktop和tsclient,其中rdesktop是基于命令行的工具,tsclient ...
- LeetCode解题报告—— Group Anagrams & Pow(x, n) & Spiral Matrix
1. Group Anagrams Given an array of strings, group anagrams together. For example, given: ["eat ...