之前讲了【AC自动姬】,今天我终于把这题给刚下来了。。。嗯,来给大家讲一讲。

题目描述:

打字机上只有28个按键,分别印有26个小写英文字母和'B'、'P'两个字母。经阿狸研究发现,这个打字机是这样工作的:

输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。

按一下印有'B'的按键,打字机凹槽中最后一个字母会消失。

按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。

例如,阿狸输入aPaPBbP,纸上被打印的字符如下:

a aa ab

我们把纸上打印出来的字符串从1开始顺序编号,一直到n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数(x,y)(其中1≤x,y≤n),打字机会显示第x个打印的字符串在第y个打印的字符串中出现了多少次。

阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?

思路分析:

  四十分算法大家都知道吧?对于每一个询问都跑KMP就行了。

  正解应该和AC自动机有关,这也应该没问题吧。

那就给大家讲两个恐怖故事吧:

这道题目,把每一个字符串都存下来会超内存。

这道题目,把每一个字符串都按照原先的方式插入trie树会超时。

嗯,听完这两个恐怖故事是不是瑟瑟发抖呢?(如果没有,那就********

那么我们先来讲一讲上面问题的处理方式吧。

看一看阿狸的打字机的运行方式,每次都不会清空,一次只加一个字符,删除也就删一个字符,想到了什么?——我们能不能一边读入,一边构造trie树呢,记录下每个单词的最后一个节点,不就可以倒着向上走,还原出各个单词了吗?

嗯,非常好,上面的两个问题就这样被我们完美解决掉了!(鼓掌

然后我们应该怎么做呢?

观察询问——第x个字符串在第y个字符串中出现了几次。

想一想,在AC自动机上,假如说,一个字符串在另一个字符串中出现会发生什么(假如说是两个字符串“shehe”和“he”)

(手画的,有点难看。。。绿色的边是fail指针)

我们发现,当he在shehe中出现时,出现he的节点3和5,fail指针都指向了7号点he。

这是为什么呢?

回顾我们上篇在强调的——fail指针指向的是当前串的部分后缀与其它模式串的前缀完全相同 的节点。

也就是说,包含he的字符串,肯定可以通过fail指针若干次跳转,来到he的节点(就是图中的7号点)。因为,它(指图中的3、5号点)有部分后缀是和he这个串的前缀完全相同的。

那么我们把所有trie树的边去掉,只留下fail指针,这样也会构成一颗树对吧,我们叫它fail树。(如下图)

也就是说,我们想要求出x字符串在y字符串中出现了几次,只需要统计fail树上,有多少属于y字符串的节点在以x字符串的结束节点为根的子树里就行了。

树上统计问题,想到了什么?——DFS序嘛!

因为以x字符串的结束节点为根的子树在dfs序上是连续的,所以我们肯定不能把它作为突破口,因为它肯定可以在log n的时间内求解(推荐用树状数组),没有必要再去对它进行讨论。

把问题进行转换——统计trie树上从根到y字符串结束节点的路径上有多少节点在fail树上是在以x字符串的结束节点为根的子树中的。

很容易想到把询问都离线出来,然后按照y归类,因为只要y相等的话,那么就可以一次性,把每个询问在log n的时间内求出来。

这样,问题就变得简单了,因为以x字符串的结束节点为根的子树在dfs序上是连续的,所以我们只要维护好从根到y字符串结束节点的路径上的节点就可以了。

其实这个东西我们也可以跟着读入的那一大坨,进行维护。

每加入一个节点就可以在trie树上走到那个节点,并且在树状数组中给它对应的dfn上+1。遇到‘B’时,就在树状数组中把之前加的1减掉,这样我们就可以保证树状数组中,只有trie树上从根到y字符串结束节点的路径上的节点在fail树上对应的dfn(貌似有点拗口),是有1的。

代码实现:

#include <bits/stdc++.h>
using namespace std;
const int maxn=100005;
char st[maxn];
string s[maxn];
vector <int> a[maxn],b[maxn];
int nxt[maxn*2],vet[maxn*2],head[maxn],trie[maxn][30],fail[maxn],his[maxn];
int dfn[maxn],end[maxn],c[maxn],ans[maxn],id[maxn],q[maxn],cnt,tot,tim,n,m,len,now;
void build(){
int head=0,tail=0;
for (int i=0;i<26;++i) if (trie[0][i]) q[++tail]=trie[0][i];
while (head!=tail){
int now=q[++head];
for (int i=0;i<26;++i)
if (trie[now][i])
q[++tail]=trie[now][i],fail[trie[now][i]]=trie[fail[now]][i];
else trie[now][i]=trie[fail[now]][i];
}
}
void add(int x,int y){
++tot;
nxt[tot]=head[x];
vet[tot]=y;
head[x]=tot;
}
void dfs(int u){
dfn[u]=++tim;
for (int i=head[u];i;i=nxt[i]) dfs(vet[i]);
end[u]=tim;
}
void update(int x,int val){
if (x<=tim) c[x]+=val,update(x+(x&-x),val);
}
int getsum(int x){
if (x>0) return c[x]+getsum(x-(x&-x));
return 0;
}
int main(){
scanf("%s",st);
for (int i=0;st[i];i++)
if (st[i]=='B') now=his[--tot];
else
if (st[i]=='P') id[++n]=now;
else {
if (!trie[now][st[i]-'a']) trie[now][st[i]-'a']=++cnt;
now=trie[now][st[i]-'a']; his[++tot]=now;
}
build(); tot=0;
for (int i=1;i<=cnt;++i) add(fail[i],i);
scanf("%d",&m); int x,y;
for (int i=1;i<=m;++i)
scanf("%d%d",&x,&y),a[id[y]].push_back(id[x]),b[id[y]].push_back(i);
dfs(0); int u=0,tot=0;
memset(his,0,sizeof(his));
for (int i=0;st[i];i++)
if (st[i]=='B') update(dfn[u],-1),u=his[--tot];
else
if (st[i]=='P') {
int siz=a[u].size();
for (int j=1;j<=siz;j++)
ans[b[u][j-1]]=getsum(end[a[u][j-1]])-getsum(dfn[a[u][j-1]]-1);
}
else u=trie[u][st[i]-'a'],his[++tot]=u,update(dfn[u],1);
for (int i=1;i<=m;++i) printf("%d\n",ans[i]);
return 0;
}

NOI 2011 【阿狸的打字机】的更多相关文章

  1. [NOI 2011]阿狸的打字机

    Description 题库链接 给你 \(n\) 个单词, \(m\) 组询问,每组询问形同 \((x,y)\) ,询问 \(x\) 串在 \(y\) 串中出现多少次. \(1\leq n,m\le ...

  2. NOI 2011 阿狸的打字机(AC自动机+主席树)

    题意 https://loj.ac/problem/2444 思路 ​多串匹配,考虑 \(\text{AC}\) 自动机.模拟打字的过程,先建出一棵 \(\text{Trie}\) 树,把它变成自动机 ...

  3. NOI 2011 阿狸的打字机 (AC自动机+dfs序+树状数组)

    题目大意:略(太长了不好描述) 良心LOJ传送门 先对所有被打印的字符串建一颗Trie树 观察数据范围,并不能每次打印都从头到尾暴力建树,而是每遍历到一个字符就在Trie上插入这个字符,然后记录每次打 ...

  4. BZOJ 2434: [Noi2011]阿狸的打字机 [AC自动机 Fail树 树状数组 DFS序]

    2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 2545  Solved: 1419[Submit][Sta ...

  5. 【BZOJ-2434】阿狸的打字机 AC自动机 + Fail树 + DFS序 + 树状数组

    2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 2022  Solved: 1158[Submit][Sta ...

  6. BZOJ_2434_[NOI2011]_阿狸的打字机_(AC自动机+dfs序+树状数组)

    描述 http://www.lydsy.com/JudgeOnline/problem.php?id=2434 给出\(n\)个字符串,\(m\)个询问,对于第\(i\)个询问,求第\(x_i\)个字 ...

  7. AC自动机:BZOJ 2434 阿狸的打字机

    2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 1834  Solved: 1053[Submit][Sta ...

  8. BZOJ 2434: [Noi2011]阿狸的打字机( AC自动机 + DFS序 + 树状数组 )

    一个串a在b中出现, 那么a是b的某些前缀的后缀, 所以搞出AC自动机, 按fail反向建树, 然后查询(x, y)就是y的子树中有多少是x的前缀. 离线, 对AC自动机DFS一遍, 用dfs序+树状 ...

  9. [NOI2011]阿狸的打字机(好题!!!!)

    2785: [NOI2011]阿狸的打字机 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 7  Solved: 3[Submit][Status][We ...

随机推荐

  1. MySQL 数据库 查 续

    MySQL 增删查改 必知必会 4.1.13 使用 like 关键字进行模糊查询 -- 说明:模糊查询,使用查询关键字like,like意思是类似于,像...的意思 -- 模糊查询,支持两种字符匹配符 ...

  2. mysql5.7.29- windows64安装教程

    1.配置环境变量 MYSQL_HOME=D:\tools\mysql-5.7. path=%MYSQL_HOME%\bin 2.执行mysqld --initialize-insecure --use ...

  3. Activiti7 网关(包含网关)

    什么是包含网关? 包含网关可以看做是排他网关和并行网关的结合体,和排他网关一样,你可以在外出顺序流上定义条件,包含网关会解析他们,但是主要的区别是包含网关可以选择多于一条顺序流,这和并行网关是一样的 ...

  4. 2020重新出发,JAVA高级,JVM

    JVM的基本概念 JVM是可运行java代码的假想计算机,包括一套字节码指令集.一组寄存器.一个栈.一个垃圾回收,堆和一个存储方法域.JVM是运行在操作系统之上的,它与硬件没有直接的交互. [外链图片 ...

  5. 深入理解Java之装箱与拆箱

    一.Java数据类型 1.在说装箱与拆箱之前,先说一下Java的基本数据类型,Java从数据类型上可以划分为值类型与引用类型,值类型是四类八种,分别是: 整数型:byte̵,short̵,int̵,l ...

  6. 【二叉树-最长路径系列(任意路径)】直径、最长同值路径、 最大路径和(DFS、树形DP)

    总述 这类题目都是求一个最长路径,这个路径可以不经过根节点. 使用dfs(即递归地遍历树)的方法.维护一个全局最长路径max作为最终结果,而递归方法dfs返回的是含根节点的最长路径.(若不使用全局变量 ...

  7. JVM关于GC的日志分析

    通过阅读GC日志,我们可以了解Java虛拟机内存分配与回收策略.内存分配与垃圾回收的参数列表 一XX: +PrintGC 输出Gc日志.类似: 一verbose:gc 一XX: +PrintGCDet ...

  8. Python爬虫开发者工具介绍

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理. chrome 开发者工具 当我们爬取不同的网站时,每个网站页面的实现方式各不相同,我们需要对 ...

  9. pytest封神之路第五步 参数化进阶

    用过unittest的朋友,肯定知道可以借助DDT实现参数化.用过JMeter的朋友,肯定知道JMeter自带了4种参数化方式(见参考资料).pytest同样支持参数化,而且很简单很实用. 语法 在& ...

  10. nginx+tomcat集群方法

    下载地址:wget http://nginx.org/download/nginx-1.16.1.tar.gz 解压:tar -zxvf 预编译 nginx+tomcat集群方法: 进入nginx配置 ...