bzoj 2780
后缀自动机的应用
首先我们观察到:如果一个询问串的答案不为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的更多相关文章
- 三种做法:BZOJ 2780: [Spoj]8093 Sevenk Love Oimaster
目录 题意 思路 AC_Code1 AC_Code2 AC_Code3 参考 @(bzoj 2780: [Spoj]8093 Sevenk Love Oimaster) 题意 链接:here 有\(n ...
- BZOJ 2780: [Spoj]8093 Sevenk Love Oimaster( 后缀数组 + 二分 + RMQ + 树状数组 )
全部串起来做SA, 在按字典序排序的后缀中, 包含每个询问串必定是1段连续的区间, 对每个询问串s二分+RMQ求出包含s的区间. 然后就是求区间的不同的数的个数(经典问题), sort queries ...
- 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 ...
- bzoj 3277 & bzoj 3473,bzoj 2780 —— 广义后缀自动机
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3277 https://www.lydsy.com/JudgeOnline/problem.p ...
- BZOJ 2780: [Spoj]8093 Sevenk Love Oimaster [广义后缀自动机]
JZPGYZ - Sevenk Love Oimaster Oimaster and sevenk love each other. But recently,sevenk hea ...
- BZOJ.2780.[SPOJ8093]Sevenk Love Oimaster(广义后缀自动机)
题目链接 \(Description\) 给定n个模式串,多次询问一个串在多少个模式串中出现过.(字符集为26个小写字母) \(Solution\) 对每个询问串进行匹配最终会达到一个节点,我们需要得 ...
- 【刷题】BZOJ 2780 [Spoj]8093 Sevenk Love Oimaster
Description Oimaster and sevenk love each other. But recently,sevenk heard that a girl named ChuYuXu ...
- bzoj 2780: [Spoj]8093 Sevenk Love Oimaster(广义SAM)
题目大意:给出n个原串,再给出m个查询串.求每个查询串出现在了多少原串中. 题解 直接对原串建一个广义SAM,然后把每一个原串放到SAM上跑一跑,记录一下每一个状态属于多少个原串,用$size$表示. ...
- BZOJ 2780 [Spoj]8093 Sevenk Love Oimaster ——广义后缀自动机
给定n个串m个询问,问每个串在n个串多少个串中出现了. 构建广义后缀自动机,(就是把所有字符串的后缀自动机合并起来)其实只需要add的时候注意一下就可以了. 然后对于每一个串,跑一边匹配,到达了now ...
随机推荐
- php面向对象之构造函数作用与方法
什么是构造函数呢?构造函数又有什么作用呢? 构造函数 ,是一种特殊的方法.主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中.特别的一个类可以有多个 ...
- Lodop打印控件传入css样式、看是否传入正确样式
Lodop中可以传入页面存在的css样式,也可以是拼接后的新样式,例如本博客的其他博文:Lodop打印如何隐藏table某一列 需要打印的页面,样式不一定都是行内样式,style样式单独写在页面上,或 ...
- kubernetes 报错汇总
一. pod的报错: 1. pod的容器无法启动报错: 报错信息: Normal SandboxChanged 4m9s (x12 over 5m18s) kubelet, k8sn1 Pod san ...
- 【LR9】【LOJ561】CommonAnts 的调和数 数论 筛法
题目大意 有一个长度为 \(n\) 的序列. 有 \(m\) 次修改,每次给你 \(x,y\),令 \(\forall 1\leq i\leq \lfloor\frac{n}{x}\rfloor,a_ ...
- java String转int int转化为String
String转int String str = "123"; int a = Integer.parseInt(str); System.out.println(a); Integ ...
- prometheus 基于文件的目标发现
prometheus 基于文件的目标发现 1.创建目录 cd /usr/local/prometheus/conf mkdir -pv targets/{nodes,docker} 2.修改prome ...
- CMDB服务器管理系统【s5day91】:资产采集相关问题
资产采集唯一标识和允许临时修改主机名 class AgentClient(BaseClient): def exec(self): obj = PluginManager() server_dict ...
- [JDK8]性能优化之使用LongAdder替换AtomicLong
如果让你实现一个计数器,有点经验的同学可以很快的想到使用AtomicInteger或者AtomicLong进行简单的封装. 因为计数器操作涉及到内存的可见性和线程之间的竞争,而Atomic***的实现 ...
- linux在线安装JDK(1.8版本)
在线下载JDK 命令: wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-sec ...
- GO 基础
基本语法练习 打印 乘法表 package main import ("fmt") func main(){ for n:=1;n<=9;n++{ for m:=1;m< ...