最近学了SAM已经SAM的比较简单的应用,SAM确实不好理解呀,记录一下。

这里提一下后缀自动机比较重要的性质:

1,SAM的点数和边数都是O(n)级别的,但是空间开两倍。

2,SAM每个结点代表一个endpos,每个endpos有可能代表多个字串(当然这些字串的endpos相等),且这些字串的长度呈一个梯形。

3,令tree[x].len为点x代表的所有字串中长度最长的,tree[x].short为最短的,那么tree[x].short=(tree[fa].len)+1,根据这条性质其实tree[x].short就不用算了可以直接由fa得到。

4,SAM的一条边代表往后添加一个字符,且路径和字串一一对应,那么就得到路径数等于字串数

5,在parent树上,x的endpos大小等于x的所有儿子y的endpos大小+1,那么就可以通过建树之后一次dfs计算所有点的endpos大小。

6,第2点说明每个状态endpos代表的长度区间为len[fa[s]]->len[s]],那么要求所有本质不同的串的个数就是∑tlen[t]−len[fa[t]] 。

模板题:洛谷P3804

题目要求出现次数不为1(即endpos大小不为1)的时候计算出现次数*字串大小最大。首先肯定要计算endpos大小(这里用的是建树之后dfs的计算办法),然后虽然每个endpos应该有多个字串,但是题目要求计算最大值,所以只看endpos最长的那个字串(tree[x].len)就可以了。

#include<bits/stdc++.h>
using namespace std;
const int N=2e6+;
int n,tot=,las=;
char s[N];
struct NODE {
int ch[];
int len,fa;
NODE(){memset(ch,,sizeof(ch));len=fa=;}
}tree[N]; int cnt=,head[N],nxt[N],to[N];
void add_edge(int x,int y) {
nxt[++cnt]=head[x]; to[cnt]=y; head[x]=cnt;
} long long ep[N],ans; //ep是结点endpos大小
void insert(int c) { //字符插入到SAM中
int p=las,np=las=++tot;ep[tot]=;
tree[np].len=tree[p].len+;
for (;p&&!tree[p].ch[c];p=tree[p].fa) tree[p].ch[c]=np;
if (!p) tree[np].fa=;
else {
int q=tree[p].ch[c];
if(tree[q].len==tree[p].len+)tree[np].fa=q;
else {
int nq=++tot;
tree[nq]=tree[q];tree[nq].len=tree[p].len+;
tree[q].fa=tree[np].fa=nq;
for(;p&&tree[p].ch[c]==q;p=tree[p].fa) tree[p].ch[c]=nq;
}
}
} void dfs(int x) {
for(int i=head[x];i;i=nxt[i]) {
int y=to[i];
dfs(y);
ep[x]+=ep[y];
}
if(ep[x]!=)ans=max(ans,ep[x]*tree[x].len);
} int main()
{
scanf("%s",s); n=strlen(s);
for(int i=;i<n;i++) insert(s[i]-'a'); //把字符串s插入到 SAM 中 for(int i=;i<=tot;i++) add_edge(tree[i].fa,i); //建树计算每个点endpos大小
dfs(); //dfs计算
printf("%lld\n",ans);
return ;
}

SPOJ1811 LCS - Longest Common Substring

求两个字符串最长公共字串长度。把字符串s1建SAM,然后令s2在SAM上匹配。匹配过程有点儿像AC自动机,一个一个字符匹配,匹配成功就继续往下匹配,当匹配失败的时候就沿着fa往上跳然后继续匹配。

这里要注意一个小细节,匹配失败往上跳的时候如果跳到了root结点记得把匹配信息清理。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+;
int n,m,tot=,las=;
char s1[N],s2[N];
struct NODE {
int ch[];
int len,fa;
NODE(){memset(ch,,sizeof(ch));len=fa=;}
}tree[N]; int cnt=,head[N],nxt[N],to[N];
void add_edge(int x,int y) {
nxt[++cnt]=head[x]; to[cnt]=y; head[x]=cnt;
} long long ep[N],ans; //ep是结点endpos大小
void insert(int c) { //字符插入到SAM中
int p=las,np=las=++tot;ep[tot]=;
tree[np].len=tree[p].len+;
for (;p&&!tree[p].ch[c];p=tree[p].fa) tree[p].ch[c]=np;
if (!p) tree[np].fa=;
else {
int q=tree[p].ch[c];
if(tree[q].len==tree[p].len+)tree[np].fa=q;
else {
int nq=++tot;
tree[nq]=tree[q];tree[nq].len=tree[p].len+;
tree[q].fa=tree[np].fa=nq;
for(;p&&tree[p].ch[c]==q;p=tree[p].fa) tree[p].ch[c]=nq;
}
}
} void dfs(int x) {
for(int i=head[x];i;i=nxt[i]) {
int y=to[i];
dfs(y);
ep[x]+=ep[y];
}
if(ep[x]!=)ans=max(ans,ep[x]*tree[x].len);
} int main()
{
scanf("%s%s",s1,s2);
n=strlen(s1); m=strlen(s2);
for(int i=;i<n;i++) insert(s1[i]-'a'); //把字符串s插入到 SAM 中 int ans=,nowlen=,now=;
for (int i=;i<m;i++,ans=max(ans,nowlen)) {
int p=s2[i]-'a';
if (tree[now].ch[p]) now=tree[now].ch[p],nowlen++;
else {
while(now&&!tree[now].ch[p]) now=tree[now].fa; //失配沿着fa继续匹配
if (now==) now=,nowlen=; //注意这里
else nowlen=tree[now].len+,now=tree[now].ch[p];
}
}
cout<<ans<<endl;
return ;
}

SPOJ7258 SUBLEX - Lexicographical Substring Search(后缀自动机)

给出一个串求它的所有字串中第k小的字串(本质相同的字串只算一个)。

根据上面提到的SAM的路径数等于字串个数,我们可以先求出以每个点为起点的路径数(即得到每个点为起点的字串数)。然后常规套路像在搜索树上一边剪枝修改k一边搜索最终得到答案。

这里要注意,因为本质相同算一个,所以每个点初始值就是1。

#include<bits/stdc++.h>
using namespace std;
const int N=2e6+;
int n,tot=,las=;
char s[N];
struct NODE {
int ch[];
int len,fa;
NODE(){memset(ch,,sizeof(ch));len=fa=;}
}tree[N]; void insert(int c) { //字符插入到SAM中
int p=las,np=las=++tot;
tree[np].len=tree[p].len+;
for (;p&&!tree[p].ch[c];p=tree[p].fa) tree[p].ch[c]=np;
if (!p) tree[np].fa=;
else {
int q=tree[p].ch[c];
if(tree[q].len==tree[p].len+)tree[np].fa=q;
else {
int nq=++tot;
tree[nq]=tree[q];tree[nq].len=tree[p].len+;
tree[q].fa=tree[np].fa=nq;
for(;p&&tree[p].ch[c]==q;p=tree[p].fa) tree[p].ch[c]=nq;
}
}
} int sub[N];
void toposort() {
static int c[N],rk[N];
for (int i=;i<=tot;i++) c[tree[i].len]++;
for (int i=;i<=n;i++) c[i]+=c[i-];
for (int i=tot;i;i--) rk[c[tree[i].len]--]=i; //前3步桶排序
for (int i=;i<=tot;i++) sub[i]=; //本质相等只算一个,初始值为1
for (int i=tot;i;i--)
for (int j=;j<;j++)
sub[rk[i]]+=sub[tree[rk[i]].ch[j]]; //该点路径数等于它所有儿子路径数和
} void solve(int k) {
int now=;
while (k) {
if (now!=) k--;
if (k<=) break;
for (int i=;i<;i++)
if (sub[tree[now].ch[i]]<k) k-=sub[tree[now].ch[i]];
else { putchar(i+'a'); now=tree[now].ch[i]; break; }
}
} int main()
{
scanf("%s",s); n=strlen(s);
for(int i=;i<n;i++) insert(s[i]-'a'); //把字符串s插入到 SAM 中 toposort(); //求DAG某个点为起点的路径数
int T,q; cin>>T;
while (T--) {
scanf("%d",&q);
solve(q); puts("");
}
return ;
}
BZOJ-3998 / 洛谷P3975 弦论
跟上题差不多,只不过是本质相同字串要算多次,所以每个点的初始值不是1而是endpos的大小。其他不用怎么变。
这题按道理字串个数要用long long。但是在BZOJ上用long long有可能超时,用int就能过。。。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+;
int n,tot=,las=;
char s[N];
struct NODE {
int ch[];
int len,fa;
NODE(){memset(ch,,sizeof(ch));len=fa=;}
}tree[N]; int ep[N],sub[N];
void insert(int c) { //字符插入到SAM中
int p=las,np=las=++tot; ep[tot]=;
tree[np].len=tree[p].len+;
for (;p&&!tree[p].ch[c];p=tree[p].fa) tree[p].ch[c]=np;
if (!p) tree[np].fa=;
else {
int q=tree[p].ch[c];
if(tree[q].len==tree[p].len+)tree[np].fa=q;
else {
int nq=++tot;
tree[nq]=tree[q];tree[nq].len=tree[p].len+;
tree[q].fa=tree[np].fa=nq;
for(;p&&tree[p].ch[c]==q;p=tree[p].fa) tree[p].ch[c]=nq;
}
}
} int c[N],rk[N];
void toposort(int opt) {
for (int i=;i<=tot;i++) c[tree[i].len]++;
for (int i=;i<=n;i++) c[i]+=c[i-];
for (int i=tot;i;i--) rk[c[tree[i].len]--]=i; //前3步桶排序 if (opt==)
for (int i=;i<=tot;i++) ep[i]=; //本质相等只算一个,初始值为1
if (opt==)
for (int i=tot;i>;i--) ep[tree[rk[i]].fa]+=ep[rk[i]]; //本质相等算多个,初始值为endpos大小
for (int i=;i<=tot;i++) sub[i]=ep[i];
for (int i=tot;i;i--)
for (int j=;j<;j++)
sub[rk[i]]+=sub[tree[rk[i]].ch[j]]; //该点路径数等于它所有儿子路径数和
} void solve(int k) {
int now=;
if (k>sub[]) { puts("-1"); return; }
while (k) {
if (now!=) k-=ep[now];
if (k<=) break;
for (int i=;i<;i++)
if (sub[tree[now].ch[i]]<k) k-=sub[tree[now].ch[i]];
else { putchar(i+'a'); now=tree[now].ch[i]; break; }
}
} int main()
{
scanf("%s",s); n=strlen(s);
for(int i=;i<n;i++) insert(s[i]-'a'); //把字符串s插入到 SAM 中 int opt,q; cin>>opt>>q;
toposort(opt); //求DAG某个点为起点的路径数
solve(q);
return ;
}
 HDU-4622 Reincarnation
题意:给出一个字符串,有多次询问[l,r],要求求字符串[l,r]字串的所有本质不同的字串个数。
解法:根据上面第6点,当我们把SAM建出来就可以通过计算sigma(tree[x].len-tree[x.fa].len)得到本质不同字串个数。但是本题要求区间不同字串个数,一看数据量2000,我们做n^2的预处理:枚举区间左端点i开始建SAM然后从左端点开始往后逐一把每个字符j加入到SAM,那么此时得到的SAM就是区间[i,j]的SAM。但是我们不能每次求出SAM暴力计算sigma(tree[x].len-tree[x.fa].len),明显超时。观察发现每插入一个字符对答案多造成的贡献就是插入字符的tree[x].len-tree[x.fa].len,所以逐一累加即可。
#include<bits/stdc++.h>
using namespace std;
const int N=4e3+;
int n,tot=,las=,sum;
char s[N];
struct NODE {
int ch[];
int len,fa;
NODE(){memset(ch,,sizeof(ch));len=fa=;}
}tree[N]; int ep[N],ans[][];
void insert(int c) { //字符插入到SAM中
int p=las,np=las=++tot; ep[tot]=;
tree[np].len=tree[p].len+;
for (;p&&!tree[p].ch[c];p=tree[p].fa) tree[p].ch[c]=np;
if (!p) tree[np].fa=;
else {
int q=tree[p].ch[c];
if(tree[q].len==tree[p].len+)tree[np].fa=q;
else {
int nq=++tot;
tree[nq]=tree[q];tree[nq].len=tree[p].len+;
tree[q].fa=tree[np].fa=nq;
for(;p&&tree[p].ch[c]==q;p=tree[p].fa) tree[p].ch[c]=nq;
}
}
sum+=tree[np].len-tree[tree[np].fa].len;
} int main()
{
int T; cin>>T;
while (T--) {
scanf("%s",s+); n=strlen(s+);
for (int i=;i<=n;i++) {
sum=;
for (int j=i;j<=n;j++) {
insert(s[j]-'a');
ans[i][j]=sum;
}
for (int j=;j<=tot;j++) {
tree[j].fa=tree[j].len=;
memset(tree[j].ch,,sizeof(tree[j].ch));
}
tot=; las=;
}
int q; cin>>q;
while (q--) {
int l,r; scanf("%d%d",&l,&r);
printf("%d\n",ans[l][r]);
}
}
return ;
}
 

后缀自动机(SAM) 学习笔记的更多相关文章

  1. 后缀自动机SAM学习笔记

    前言(2019.1.6) 已经是二周目了呢... 之前还是有一些东西没有理解到位 重新写一下吧 后缀自动机的一些基本概念 参考资料和例子 from hihocoder DZYO神仙翻译的神仙论文 简而 ...

  2. SAM学习笔记

    SAM学习笔记 后缀自动机(模板)NSUBSTR(Caioj1471 || SPOJ 8222) [题意] 给出一个字符串S(S<=250000),令F(x)表示S的所有长度为x的子串中,出现次 ...

  3. [转]后缀自动机(SAM)

    原文地址:http://blog.sina.com.cn/s/blog_8fcd775901019mi4.html 感觉自己看这个终于觉得能看懂了!也能感受到后缀自动机究竟是一种怎样进行的数据结构了. ...

  4. SPOJ 1811. Longest Common Substring (LCS,两个字符串的最长公共子串, 后缀自动机SAM)

    1811. Longest Common Substring Problem code: LCS A string is finite sequence of characters over a no ...

  5. 后缀自动机(SAM)奶妈式教程

    后缀自动机(SAM) 为了方便,我们做出如下约定: "后缀自动机" (Suffix Automaton) 在后文中简称为 SAM . 记 \(|S|\) 为字符串 \(S\) 的长 ...

  6. 【算法】后缀自动机(SAM) 初探

    [自动机] 有限状态自动机的功能是识别字符串,自动机A能识别字符串S,就记为$A(S)$=true,否则$A(S)$=false. 自动机由$alpha$(字符集),$state$(状态集合),$in ...

  7. 浅谈后缀自动机SAM

    一下是蒟蒻的个人想法,并不很严谨,仅供参考,如有缺误,敬请提出 参考资料: 陈立杰原版课件 litble 某大神 某大神 其实课件讲得最详实了 有限状态自动机 我们要学后缀自动机,我们先来了解一下自动 ...

  8. 【文文殿下】后缀自动机(Suffix Automaton,SAM)学习笔记

    前言 后缀自动机是一个强大的数据结构,能够解决很多字符串相关的(String-related)问题. 例如:他可以查询一个字符串在另一个字符串中出现的所有子串,以及查询一个字符串中本质不同的字符串的个 ...

  9. [学习笔记]后缀自动机SAM

    好抽象啊,早上看了两个多小时才看懂,\(\%\%\%Fading\) 早就懂了 讲解就算了吧--可以去看看其他人的博客 1.[模板]后缀自动机 \(siz\) 为该串出现的次数,\(l\) 为子串长度 ...

随机推荐

  1. pandas.Series函数用法

    class pandas.Series(data=None, index=None, dtype=None, name=None, copy=False, fastpath=False) e.g., ...

  2. bootstrap 的布局

    第一步:你要做的就是选择适合你显示器的标签: .col-xs- 超小屏幕 手机 (<768px) .col-sm- 小屏幕 平板 (≥768px) .col-md- 中等屏幕 桌面显示器 (≥9 ...

  3. hdu 6050: Funny Function (2017 多校第二场 1006) 【找规律】

    题目链接 暴力打个表找下规律就好了,比赛时看出规律来了倒是,然而看这道题看得太晚了,而且高中的那些数列相关的技巧生疏了好多,然后推公式就比较慢..其实还是自身菜啊.. 公式是 #include< ...

  4. Intent.java分析

    代码位于frameworks/base/core/java/anroid/Content/Intent.java Intent是对要进行操作的一种抽象描述.用action抽象操作,用data(andr ...

  5. Jira插件安装

    以下操作需要反编译 1.反编译的jar包 1)E:\JIRA安装路径\atlassian-jira\WEB-INF\atlassian-bundled-plugins\atlassian-univer ...

  6. OC + RAC (九) 过滤

    // 跳跃 : 如下,skip传入2 跳过前面两个值 // 实际用处: 在实际开发中比如 后台返回的数据前面几个没用,我们想跳跃过去,便可以用skip - (void)skip { RACSubjec ...

  7. C语言 为什么要引入指针?

    https://blog.csdn.net/chengxuyuan997/article/details/81231679 正文 在说为什么引入指针这个问题前先带大家了解一下什么是指针? 指针最为简短 ...

  8. 27 August

    高精度 struct bigint{ int a[1000],an; bigint operator = (int b){ an=0; while (b){a[an++]=b%10;b/=10;} r ...

  9. Linux C编程

    Linux C网络编程 1.Linux套接字 1.1 套接字介绍 套接字(Sockets),即为网络进程ID,是由运行这个进程的计算机的IP地址和这个进程使用的端口(Port)组成. 可以只用'net ...

  10. SQL SERVER 2012文件表(FILETABLE)新体验之一

    SQLSERVER 2012 文件表功能很COOL,让我们体验一下吧. 1,创建数据库 [sql] DREATE DATABASE FileTableTest ON  PRIMARY (     NA ...