后缀自动机的应用

首先我们观察到:如果一个询问串的答案不为0,那么这个串一定是至少一个模式串的子串

如果只有一个模式串,那么这个问题可以简单地用什么东西解决掉(比如普通后缀自动机)

而这里有很多模式串,所以普通后缀自动机是不够的。

那么我们提出广义后缀自动机

所谓广义后缀自动机,可以简单理解成将很多个串建在同一个后缀自动机上

所以它的构造就是:每插入完毕一个串,就将las指针指回根节点,然后去构造下一个串就可以了

好像很简单?

上面的构造方法是不准确的!

这里转载一位dalao的博客,他详尽地给出了如上的构造方法的错误之处以及正确的方法

(但是太恶心了,而且一般错误的方法也不会被卡,所以我们这里还是使用了错误方法,但是在这里要有个印象,这并不是完全正确的构造方法)

接下来是转载部分:

"由于之前的疏忽,一直认为广义后缀自动机的构建方法就是在普通后缀自动机上直接插入多串,在此修正

原先的方法:对第一个串建后缀自动机,在插入第二个串时将fin指针指向根,然后暴力插入第二个字符串。

这种方法的错误:

当原来的字符串集合中含有这个字符串时,便会多建立新点,这个点并没有被任何点的tranc指向。

为什么之前一直没出问题:

我做题太少了

在大多数情况下,这个点是不会更新且不会被更新的,而且它前后的点都表现正常。

所以,在所有串都是静态的,就是一遍建成在大多数情况下是不会出现问题的。

那什么时候出现的问题:

当这个字符串集合是动态的(只插入不删除)时,一般我们使用LCT来维护Parent树。

这时,当我们拎出链进行修改时这个点就会参与运算,(然而这个点还没有记录原来修改的值)所以重复字符串/字符串前缀是需要在建立广义后缀自动机时进行特判处理

广义后缀自动机的本质:

普通后缀自动机是依靠字符串建立起来的。

而广义后缀自动机原则上是在原字符串集合的trie树上建立的,原则上要先建出trie树,然后记录每个点的fin指针。

每个节点在构建时需要在父亲的fin上构造。

原则上trie树的遍历可以使用dfs和bfs,但是dfs可以被构造的数据卡成O(n2)O(n2)

所以原则上要使用bfs来建广义后缀自动机。

但是考虑我们要解决的问题,也就是trie上构造和直接插入本质上的区别,就是trie树省略了前缀的重复

从这个性质入手,可以直接将后缀树的节点破开(特判一下就好了,就是前缀重复时像trie一样搞就好了)”

转载部分结束

接下来进入正题

我们还是按照老方法构造广义后缀自动机,本题不会被卡

然后我们分析一下题意:

如果一个询问串是一个模式串的子串,那么这个询问串一定是这个模式串的一个前缀的后缀(虽然我们常用的定义是后缀的前缀,但是在这个“后缀自动机”里我们使用第一个定义更容易理解)

那么,基于后缀自动机的pre指针的定义,我们发现:一个pre指针指向的点所对应的串一定是原节点对应字符串的一个后缀!

那么,如果我们将pre指针反指,就会得到一个树形结构,我们称这棵树叫parent树

我们可以发现,parent树的一个父节点对应的串是它所有子代节点(即儿子,儿子的儿子...)所对应串的子串!

以上内容,是在学习后缀自动机时应当了解到的,其实是基础知识,但是还是要介绍一下

那么对于这道题,我们发现:考虑到如果答案不为0,那么后缀自动机一定能识别这个串

所以我们可以先用建好的后缀自动机去识别每一个串,以此就可以查出所有答案为0的部分

然后,我们记录下答案不为0的询问串的结束位置,然后我们建起parent树求出dfs序,那么我们只需求出对于每个结束位置,它在parent树上的子树内有多少个不同的endpos即可(在广义后缀自动机上,用不同的endpos区分不同的串)

这一点可以利用dfs序实现

具体来讲,求出parent树的dfs之后,基于dfs序的性质,问题就转变成了在一段区间内(即询问节点的入栈序与出栈序)之间不同数值的个数

那么这类似于bzoj 1878,HH的项链

我们只需离线所有询问,然后用树状数组搞就可以了。

具体看代码

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
struct SAM
{
int tranc[27];
int endpos;
int pre;
int len;
}s[200005];
struct Edge
{
int next;
int to;
}edge[200005];
struct Ques
{
int lq,rq,num;
}q[60005];
int cnt=1;
int n,m;
char ch[360005];
int head[200005];
int inr[200005];
int our[200005];
int sum[400005];
int last[200005];
int ret[60005];
int f[400005];
int las,siz;
int dep;
int tot;
void init()
{
memset(head,-1,sizeof(head));
cnt=1;
}
bool cmp(Ques x,Ques y)
{
return x.rq<y.rq;
}
int lowbit(int x)
{
return x&(-x);
}
void add(int l,int r)
{
edge[cnt].next=head[l];
edge[cnt].to=r;
head[l]=cnt++;
}
void update(int x,int y)
{
while(x<=dep)
{
sum[x]+=y;
x+=lowbit(x);
}
}
int get_sum(int x)
{
int ans=0;
while(x)
{
ans+=sum[x];
x-=lowbit(x);
}
return ans;
}
void ins(int c,int typ)
{
int nwp=++siz;
s[nwp].endpos=typ;
s[nwp].len=s[las].len+1;
int lsp;
for(lsp=las;lsp&&!s[lsp].tranc[c];lsp=s[lsp].pre)s[lsp].tranc[c]=nwp;
if(!lsp)
{
s[nwp].pre=1;
}else
{
int lsq=s[lsp].tranc[c];
if(s[lsq].len==s[lsp].len+1)
{
s[nwp].pre=lsq;
}else
{
int nwq=++siz;
s[nwq]=s[lsq];
s[nwq].len=s[lsp].len+1;
s[nwq].endpos=0;
s[lsq].pre=s[nwp].pre=nwq;
while(s[lsp].tranc[c]==lsq)s[lsp].tranc[c]=nwq,lsp=s[lsp].pre;
}
}
las=nwp;
}
void buildtree()
{
init();
for(int i=2;i<=siz;i++)add(s[i].pre,i);
}
void dfs(int x)
{
inr[x]=++dep;
f[dep]=x;
for(int i=head[x];i!=-1;i=edge[i].next)
{
int to=edge[i].to;
dfs(to);
}
our[x]=++dep;
}
int check(int l)
{
int laas=1;
for(int i=1;i<=l;i++)
{
if(s[laas].tranc[ch[i]-'a'+1])laas=s[laas].tranc[ch[i]-'a'+1];
else return 0;
}
return laas;
}
int main()
{
scanf("%d%d",&n,&m);
las=++siz;
for(int i=1;i<=n;i++)
{
scanf("%s",ch+1);
int len=strlen(ch+1);
for(int j=1;j<=len;j++)ins(ch[j]-'a'+1,i);
las=1;
}
buildtree();
dfs(1);
for(int i=1;i<=m;i++)
{
scanf("%s",ch+1);
int len=strlen(ch+1);
int t=check(len);
if(t)
{
q[++tot].lq=inr[t];
q[tot].rq=our[t];
q[tot].num=i;
}
}
sort(q+1,q+tot+1,cmp);
int ttop=1;
for(int i=1;i<=dep;i++)
{
update(i,1);
if(last[s[f[i]].endpos])update(last[s[f[i]].endpos],-1);
last[s[f[i]].endpos]=i;
while(q[ttop].rq==i)
{
ret[q[ttop].num]=get_sum(q[ttop].rq)-get_sum(q[ttop].lq-1)-1;
ttop++;
}
}
for(int i=1;i<=m;i++)printf("%d\n",ret[i]);
return 0;
}

bzoj 2780的更多相关文章

  1. 三种做法:BZOJ 2780: [Spoj]8093 Sevenk Love Oimaster

    目录 题意 思路 AC_Code1 AC_Code2 AC_Code3 参考 @(bzoj 2780: [Spoj]8093 Sevenk Love Oimaster) 题意 链接:here 有\(n ...

  2. BZOJ 2780: [Spoj]8093 Sevenk Love Oimaster( 后缀数组 + 二分 + RMQ + 树状数组 )

    全部串起来做SA, 在按字典序排序的后缀中, 包含每个询问串必定是1段连续的区间, 对每个询问串s二分+RMQ求出包含s的区间. 然后就是求区间的不同的数的个数(经典问题), sort queries ...

  3. bzoj 3277 串 && bzoj 3473 字符串 && bzoj 2780 [Spoj]8093 Sevenk Love Oimaster——广义后缀自动机

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3277 https://www.lydsy.com/JudgeOnline/problem.p ...

  4. bzoj 3277 & bzoj 3473,bzoj 2780 —— 广义后缀自动机

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3277 https://www.lydsy.com/JudgeOnline/problem.p ...

  5. BZOJ 2780: [Spoj]8093 Sevenk Love Oimaster [广义后缀自动机]

    JZPGYZ - Sevenk Love Oimaster     Oimaster and sevenk love each other.       But recently,sevenk hea ...

  6. BZOJ.2780.[SPOJ8093]Sevenk Love Oimaster(广义后缀自动机)

    题目链接 \(Description\) 给定n个模式串,多次询问一个串在多少个模式串中出现过.(字符集为26个小写字母) \(Solution\) 对每个询问串进行匹配最终会达到一个节点,我们需要得 ...

  7. 【刷题】BZOJ 2780 [Spoj]8093 Sevenk Love Oimaster

    Description Oimaster and sevenk love each other. But recently,sevenk heard that a girl named ChuYuXu ...

  8. bzoj 2780: [Spoj]8093 Sevenk Love Oimaster(广义SAM)

    题目大意:给出n个原串,再给出m个查询串.求每个查询串出现在了多少原串中. 题解 直接对原串建一个广义SAM,然后把每一个原串放到SAM上跑一跑,记录一下每一个状态属于多少个原串,用$size$表示. ...

  9. BZOJ 2780 [Spoj]8093 Sevenk Love Oimaster ——广义后缀自动机

    给定n个串m个询问,问每个串在n个串多少个串中出现了. 构建广义后缀自动机,(就是把所有字符串的后缀自动机合并起来)其实只需要add的时候注意一下就可以了. 然后对于每一个串,跑一边匹配,到达了now ...

随机推荐

  1. vuex学习总结

    vuex 学习 mapState,mapGetters 一般也写在 computed 中 , mapActions 一般写在 methods中.

  2. EQueue

    EQueue 2.3.2版本发布(支持高可用) - dotNET跨平台 - CSDN博客https://blog.csdn.net/sD7O95O/article/details/78097193 E ...

  3. D3.js 入门学习(一)

    一.安装D3.js 1.网络连接 <script src="https://d3js.org/d3.v4.min.js"></script> 2.命令行安装 ...

  4. echo与print,var_dump()和print_r()的区别

    1.echo 和 print 的区别 共同点:首先echo 和 print 都不是严格意义上的函数,他们都是 语言结构;他们都只能输出 字符串,整型跟int型浮点型数据.不能打印复合型和资源型数据: ...

  5. python之路6-迭代器、生成器、装饰器

    1.迭代器&生成器 列表生成式 现在有个需求,列表[1,2,3,4,5,6,7,,8,9],要求把列表里的每个值加1,如何实现? 方法一: list = [1,2,3,4,5,6,7,8,9] ...

  6. MySQL 中触发器的应用

    在一个教育系统里面,有 科目表 ,章节表(每一科目对应若干大章节),小节表(每一大章节下面有若干小节),习题表(每一小节对应若干习题), 在后台管理系统中 有这样几个功能要实现,在 科目列表页面中  ...

  7. Django+Vue打造购物网站(十一)

    第三方登录 微博创建应用,修改回调地址 http://open.weibo.com/authentication 安装第三方登录插件 https://github.com/python-social- ...

  8. java实现sftp客户端上传文件夹的功能

    使用的jar: <dependencies> <dependency> <groupId>jsch</groupId> <artifactId&g ...

  9. 20175221 2018-2019-2 《Java程序设计》第二周学习总结

    20175221   <Java程序设计>第2周学习总结 教材学习内容总结 教材方面 本周学习了第二章的“基本数据类型与数组”的内容,以及粗略地看了一下第三章“运算符.表达式和语句”的内容 ...

  10. c/c++学习系列之memset()函数

    [转载] memset 函数是内存赋值函数,用来给某一块内存空间进行赋值的: 包含在<string.h>头文件中,可以用它对一片内存空间逐字节进行初始化: 原型为 : void *mems ...