参考

还有首先你要会SAM吧~

用途

相比与单串SAM,广义自动机能存储的是多个字符串

有两种写法,第一种是离线利用trie树结构,第二种是在线伪广义SAM

离线+Trie

首先构建出trie树。

然后在trie树上BFS(),用\(pos[u]\)映射trie树上\(u\)节点对应SAM上的节点。

为什么不dfs,因为时间复杂度是trie树上所有叶子到根的距离和,证明BFS\(O(n)\)复杂度具体见上面参考博客

code:

struct SAM {
int tr[N<<1][M],nd,len[N<<1],par[N<<1],pos[N*M];
queue<int> Q;
int Insert(int x,int lst) {
...略,返回新节点编号
}
void BFS() {
Q.push(1);pos[1]=1;
while(!Q.empty()) {
int u=Q.front(); Q.pop();
for(int i=0;i<26;i++) {
int v=T.go[u][i];
if(!v)continue;
pos[v]=Insert(i,pos[u]);
Q.push(v);
}
}
}S;

相信上面code的你能够很好的理解

在线

虽然它很伪,但通常跑的比上面那种快,而且对于很多问题在上面处理起来很方便。

方便讲解,先放一份原来插单串的code:

int Insert(int x,int lst) {
int p=lst,np=++nd;len[np]=len[p]+1;
for(;!tr[p][x];p=par[p])tr[p][x]=np;
if(!p) {par[np]=1;}
else {
int q=tr[p][x];
if(len[q]==len[p]+1) {par[np]=q;}
else {
int nq=++nd;par[nq]=par[q];len[nq]=len[p]+1;
for(int j=0;j<26;j++)tr[nq][j]=tr[q][j];
par[q]=par[np]=nq;
for(;tr[p][x]==q;p=par[p]) tr[p][x]=nq;
}
}
return np;
}

多串总体的区别就是每加入一个新串前让lst=1(root)

这样会出现之前插单串没出现过的情况:之前p=lst往上找tr[p][x]!=0之前肯定会存在tr[p][x]=0

然而现在可能一开始tr[lst][x]!=0,这有什么问题吗?就跟原来一样分两类讨论(拆点或不拆点)

问题就在于,这时新加的np是个空点,因为它是完全没有必要的。

令q=tr[lst][x]

  • 如果len[q]=len[lst]+1,那此时直接返回q即可。
  • 否则len[q]>len[lst]+1,还是要拆出nq,但是在(上面拆点)代码中唯一用到新点(np)的就是par[np]=nq

    可len[nq]=len[np]的,nq已经包含了np了……

    So Easy!直接不创np这个空点不就行了

    我们上面也说明了现在新代码的写法。先判断tr[lst][x]!=0时就不定义新np点……最后return nq,否则写法跟上面单串一样的。
  • code
struct SAM {
int tr[N<<1][M],nd,len[N<<1],par[N<<1];
queue<int> Q;
SAM() {nd=1;}
int Insert(int x,int lst) {
int p=lst;
if(tr[p][x]) {
int q=tr[p][x];
if(len[q]==len[p]+1)return q;
int nq=++nd;par[nq]=par[q];len[nq]=len[p]+1;
for(int j=0;j<c;j++)tr[nq][j]=tr[q][j];
for(;tr[p][x]==q;p=par[p]) tr[p][x]=nq;
return par[q]=nq;
}
int np=++nd;len[np]=len[p]+1;
for(;!tr[p][x];p=par[p])tr[p][x]=np;
if(!p) {par[np]=1;}
else {
int q=tr[p][x];
if(len[q]==len[p]+1) {par[np]=q;}
else {
int nq=++nd;par[nq]=par[q];len[nq]=len[p]+1;
for(int j=0;j<c;j++)tr[nq][j]=tr[q][j];
par[q]=par[np]=nq;
for(;tr[p][x]==q;p=par[p]) tr[p][x]=nq;
}
}
return np;
}
}S;

ps.最后强调一点:每个结点中的子串可能来自多个字符串。然而所有来源于相同字符串的子串endpos集合保证相同,而来源于不同字符串的endpos不一定相同.

很好理解(后面加一堆废话),假如加入了i-1个字符串(此时SAM满足上面性质)。第i个字符串的加入,会拆分原来的点(拆点不影响上面性质,只不过是对于同一个字符串同一个等价类被拆成多个节点罢了)。新加的i字符串肯定是满足的。

广义SAM的更多相关文章

  1. 【HDU 4436】 str2int (广义SAM)

    str2int Problem Description In this problem, you are given several strings that contain only digits ...

  2. 【BZOJ 3926】 [Zjoi2015]诸神眷顾的幻想乡 (广义SAM)

    3926: [Zjoi2015]诸神眷顾的幻想乡 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 974  Solved: 573 Descriptio ...

  3. 【BZOJ 3473】 字符串 (后缀数组+RMQ+二分 | 广义SAM)

    3473: 字符串 Description 给定n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中至少k个字符串的子串? Input 第一行两个整数n,k. 接下来n行每行一个字符串 ...

  4. luogu3346 诸神眷顾的幻想乡 (广义SAM)

    首先,让每一个叶节点做一次树根的话,每个路径一定至少有一次会变成直上直下的 于是对于每个叶节点作为根产生的20个trie树,把它们建到同一个广义SAM里 建法是对每个trie dfs去建,last就是 ...

  5. loj#6031. 「雅礼集训 2017 Day1」字符串(SAM 广义SAM 数据分治)

    题意 链接 Sol \(10^5\)次询问每次询问\(10^5\)个区间..这种题第一感觉就是根号/数据分治的模型. \(K\)是个定值这个很关键. 考虑\(K\)比较小的情况,可以直接暴力建SAM, ...

  6. Luogu P3181 [HAOI2016]找相同字符 广义$SAM$

    题目链接 \(Click\) \(Here\) 设一个串\(s\)在\(A\)中出现\(cnt[s][1]\)次,在\(B\)中出现\(cnt[s][2]\)次,我们要求的就是: \[\sum cnt ...

  7. CF666E Forensic Examination 广义SAM、线段树合并、倍增、扫描线

    传送门 朴素想法:对\(M\)个匹配串\(T_1,...,T_M\)建立广义SAM,对于每一次询问,找到这个SAM上\(S[pl...pr]\)对应的状态,然后计算出对于每一个\(i \in [l,r ...

  8. Luogu4022 CTSC2012 熟悉的文章 广义SAM、二分答案、单调队列

    传送门 先将所有模板串扔进广义SAM.发现作文的\(L0\)具有单调性,即\(L0\)更小不会影响答案,所以二分答案. 假设当前二分的值为\(mid\),将当前的作文放到广义SAM上匹配. 设对于第\ ...

  9. BZOJ3926 ZJOI2015 诸神眷顾的幻想乡 Trie、广义SAM

    传送门 树上的任意一条路径一定会在以某一个叶子节点为根的树上成为一条直上直下的链,而总共只有\(20\)个叶子节点. 于是每一次选所有叶子节点中的一个作为根,形成一个\(Trie\),把\(20\)个 ...

  10. hdu6405Make ZYB Happy 广义sam

    题意:给出n(n<=10000)个字符串S[1~n],每个S[i]有权值val[i],随机等概率造一个由小写字母构成的字符串T,Sum = 所有含有子串T的S[i]的val[i]之积,求Sum的 ...

随机推荐

  1. 大数据学习之路之ambari配置(三)

    添加了虚拟机内存空间 重装ambari

  2. source /etc/profile 不起作用?

    给Linux配置了环境变量,source /etc/profile 完成之后只在当前用户下起作用,切换用户后设置的环境变量竟然没有生效!重启后虽然生效了,但是想知道怎么回事. 找到了如下解答: 假设你 ...

  3. JS 实现下拉框回显

    JS 实现下拉框回显 学习内容: 需求 总结: 学习内容: 需求 用 JS 实现下拉框回显 实现代码 <!DOCTYPE html> <html lang="en" ...

  4. Promise原理实现(一):前置知识点

      实现promise首先需要了解如下知识点: 1: 高阶函数 (一个函数作为另外一个函数的参数,这个包含的函数就是高阶函数): outer是一个高阶函数,inner函数作为一个参数传递:此处也是闭包 ...

  5. nodejs制作爬虫程序

    在nodejs中,可以通过不断对服务器进行请求,以及本身的fs  =>filesystem 模块和clientRequest模块对网站的资源进行怕取,目前只做到了对图片的趴取!视频文件格式各异, ...

  6. 猿人学python爬虫第一题

    打开网站.F12,开启devtools.发现有段代码阻止了我们调试 好的.接下来有几种解决方法 1- 绕过阻止调试方法 方法1(推荐) 鼠标放在debugger该行,左边数字行号那一列.右键选择不在永 ...

  7. I/O 引脚

    我们以网卡举例 引脚,芯片,pcb板之间的关系非常紧密 1.引脚,又叫管脚,英文叫Pin. 就是从集成电路(芯片)内部电路引出与外围电路的接线,所有的引脚就构成了这块芯片的接口.引线末端的一段,通过软 ...

  8. python入门基础-介绍、基础语法

    一.anaconda下的spyder简介 Spyder 是一个强大的交互式 Python 语言开发环境,提供高级的代码编辑.交互测试.调试等特性,支持包括 Windows.Linux 和 OS X 系 ...

  9. 重定向管道流读取TXT文本第一次读取为""空字符串、type xxx.txt | go run . 报错、BOM头、[239,186,191] 字节数组

    重定向管道流读取TXT文本第一次读取为""空字符串.type xxx.txt | go run . 报错.BOM头.[239 186 191] 字节数组

  10. 《手把手教你》系列基础篇(八十八)-java+ selenium自动化测试-框架设计基础-Log4j 2实现日志输出-下篇(详解教程)

    1.简介 上一篇宏哥讲解和分享了如何在控制台输出日志,但是你还需要复制粘贴才能发给相关人员,而且由于界面大小限制,你只能获取当前的日志,因此最好还是将日志适时地记录在文件中直接打包发给相关人员即可.因 ...