【BZOJ2434】[NOI2011]阿狸的打字机 AC自动机+DFS序+树状数组
【BZOJ2434】[NOI2011]阿狸的打字机
Description
阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有28个按键,分别印有26个小写英文字母和'B'、'P'两个字母。
经阿狸研究发现,这个打字机是这样工作的:
l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。
l 按一下印有'B'的按键,打字机凹槽中最后一个字母会消失。
l 按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。
例如,阿狸输入aPaPBbP,纸上被打印的字符如下:
a
aa
ab
我们把纸上打印出来的字符串从1开始顺序编号,一直到n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数(x,y)(其中1≤x,y≤n),打字机会显示第x个打印的字符串在第y个打印的字符串中出现了多少次。
阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?
Input
输入的第一行包含一个字符串,按阿狸的输入顺序给出所有阿狸输入的字符。 第二行包含一个整数m,表示询问个数。 接下来m行描述所有由小键盘输入的询问。其中第i行包含两个整数x, y,表示第i个询问为(x, y)。
Output
输出m行,其中第i行包含一个整数,表示第i个询问的答案。
Sample Input
Sample Output
HINT
题解:先构建fail树,然后对于一个询问a b,我们就可以将其转化成 在以a为根的子树(这里的子树指fail树)中,有多少个节点在单词b中出现过,于是我们自然想到要用离线处理来搞。就是在DFS遍历fail树的时候,分别记录 从上面查询到该点 和 从下面回溯到该点 时,有多少个节点在b中出现过,两者的差就是我们要的答案,我们只需要把 询问 以 链表 的形式存储到a上就可以了。
但我们发现一个问题,对于某一个节点,它可能存在于很多个单词之中,那我们搜到这个点时就要把这些单词出现的次数全部+1,怎么办?
我们不妨分析一下AC自动机的性质,对于所有包含节点x的单词,他们的结束点一定在以x为根的子树中(这里的子树指AC自动机)。仔细观察发现,这些结束点在AC自动机的DFS序中一定是一段连续的区间(中间没有其它结束点)。利用这个性质,我们就可以对于每一个节点x,求出它对应的区间,然后将整个区间+1,这个可以用树状数组实现。
(以上都是个人想法,也许麻烦了,但事实证明可以AC)
这么做的恶心之处就在于:1.对于一个结束点,既要知道它在AC自动机中的的编号,又要知道它在询问中的编号,还要知道它在DFS序中的编号,特别容易搞混。2.DFS要进行两遍,一遍在AC自动机上搞,一遍在fail树上搞。3.我们一开始求出来的fail树是从儿子指向父亲的,我们还要正向再建一遍树。4.一个结束点可能对应多个单词。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
const int maxn=100010;
struct node
{
int ch[26],fail,l,r,cnt;
}p[maxn];
char str[maxn];
int sta[maxn],tp,s[maxn],pos[maxn];
int n,tot,len,sum,now;
int t1[maxn],n1[maxn],h1[maxn],ans[maxn],n2[maxn],h2[maxn];
int tree[maxn];
queue<int> q;
void updata(int x,int v)
{
for(int i=x;i<=sum;i+=i&-i) tree[i]+=v;
}
int query(int x)
{
int i,ret=0;
for(i=x;i;i-=i&-i) ret+=tree[i];
return ret;
}
void dfs1(int x) //遍历AC自动机
{
p[x].l=p[x].r=now; //如果这个点是结束点,那么now就是该点在DFS序中的顺序
now+=p[x].cnt; //[l,r]就是这个点所影响的单词的区间
for(int i=0;i<26;i++)
{
if(p[x].ch[i])
{
dfs1(p[x].ch[i]);
p[x].r=max(p[x].r,p[p[x].ch[i]].r);
}
}
}
void build()
{
int i,j,u,t;
q.push(1);
while(!q.empty())
{
u=q.front(),q.pop();
for(i=0;i<26;i++) //这里不要用fail修改儿子了,因为还要DFS
{
if(!p[u].ch[i]) continue;
q.push(p[u].ch[i]);
t=p[u].fail;
while(!p[t].ch[i]&&t) t=p[t].fail;
if(t) p[p[u].ch[i]].fail=p[t].ch[i];
else p[p[u].ch[i]].fail=1;
}
}
}
void dfs(int x) //遍历fail树
{
int i;
for(i=h1[x];i;i=n1[i])
{
ans[i]-=query(p[t1[i]].l);
}
updata(p[x].l,1);
updata(p[x].r+1,-1);
for(i=h2[x];i;i=n2[i])
{
dfs(i);
}
for(i=h1[x];i;i=n1[i]) //两次差值就是答案,意会一下
{
ans[i]+=query(p[t1[i]].l);
}
}
int main()
{
scanf("%s%d",str,&n);
len=strlen(str);
int i,j,k,u,t,a,b;
u=sta[++tp]=1;
tot=1;
for(i=0;i<len;i++) //用栈搞一搞,输入其实很简单
{
if(str[i]=='B') u=sta[--tp];
else if(str[i]=='P') p[u].cnt++,pos[++sum]=u;
else
{
if(!p[u].ch[str[i]-'a']) p[u].ch[str[i]-'a']=++tot;
u=p[u].ch[str[i]-'a'];
sta[++tp]=u;
}
}
for(i=1;i<=n;i++)
{
scanf("%d%d",&a,&b); //将询问存储到a节点上
t1[i]=pos[b];
n1[i]=h1[pos[a]];
h1[pos[a]]=i;
}
build();
now=1;
dfs1(1);
for(i=2;i<=tot;i++) //重建fail树
{
n2[i]=h2[p[i].fail];
h2[p[i].fail]=i;
}
dfs(1);
for(i=1;i<=n;i++) printf("%d\n",ans[i]);
return 0;
}
【BZOJ2434】[NOI2011]阿狸的打字机 AC自动机+DFS序+树状数组的更多相关文章
- BZOJ2434[Noi2011]阿狸的打字机——AC自动机+dfs序+树状数组
题目描述 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母. 经阿狸研究发现,这个打字机是这样工作的: l 输入小 ...
- BZOJ 2434: [Noi2011]阿狸的打字机( AC自动机 + DFS序 + 树状数组 )
一个串a在b中出现, 那么a是b的某些前缀的后缀, 所以搞出AC自动机, 按fail反向建树, 然后查询(x, y)就是y的子树中有多少是x的前缀. 离线, 对AC自动机DFS一遍, 用dfs序+树状 ...
- NOI 2011 阿狸的打字机 (AC自动机+dfs序+树状数组)
题目大意:略(太长了不好描述) 良心LOJ传送门 先对所有被打印的字符串建一颗Trie树 观察数据范围,并不能每次打印都从头到尾暴力建树,而是每遍历到一个字符就在Trie上插入这个字符,然后记录每次打 ...
- BZOJ 2434 阿狸的打字机(ac自动机+dfs序+树状数组)
题意 给你一些串,还有一些询问 问你第x个串在第y个串中出现了多少次 思路 对这些串建ac自动机 根据fail树的性质:若x节点是trie中root到t任意一个节点的fail树的祖先,那么x一定是y的 ...
- BZOJ2434: [NOI2011]阿狸的打字机(AC自动机+dfs序+树状数组)
[NOI2011]阿狸的打字机 题目链接:https://www.luogu.org/problemnew/show/P2414 题目背景 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机. ...
- BZOJ_2434_[NOI2011]_阿狸的打字机_(AC自动机+dfs序+树状数组)
描述 http://www.lydsy.com/JudgeOnline/problem.php?id=2434 给出\(n\)个字符串,\(m\)个询问,对于第\(i\)个询问,求第\(x_i\)个字 ...
- CodeForces -163E :e-Government (AC自动机+DFS序+树状数组)
The best programmers of Embezzland compete to develop a part of the project called "e-Governmen ...
- BZOJ_3881_[Coci2015]Divljak_AC自动机+dfs序+树状数组
BZOJ_3881_[Coci2015]Divljak_AC自动机+dfs序+树状数组 Description Alice有n个字符串S_1,S_2...S_n,Bob有一个字符串集合T,一开始集合是 ...
- BZOJ2434: [Noi2011]阿狸的打字机(AC自动机 树状数组)
Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 4140 Solved: 2276[Submit][Status][Discuss] Descript ...
随机推荐
- spring—Bean配置
Spring是一个开源的框架,其目标是简化java的开发.为了降低Java开发的复杂性,Spring有如下的特性: >> 基于POJO的轻量级和最小侵入性编程 >> 通过依赖注 ...
- MySQL 性能优化的最佳 20+ 条经验
今天,数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显.关于数据库的性能,这并不只是DBA才需要担心的事,而这更是我 们程序员需要去关注的事情.当我们去设计数据库表结构,对操作数 ...
- ggplot2——简介
ggplot2是R语言最为强大的作图软件包,强于其自成一派的数据可视化理念.当熟悉了ggplot2的基本套路后,数据可视化工作将变得非常轻松而有条理. 本文主要对ggplot2的可视化理念及开发套路做 ...
- python中copy 与 '=' 的区别
当你a=1000的时候a指向一个新的类,内容为1000,而b仍然指向原来指向的内容,因为你没有叫它指向其他内容.你使用=符号,使得a和b指向同一个内容,而copy则是将b的内容复制后让c指向这个拷贝的 ...
- 第三百零五节,Django框架,Views(视图函数),也就是逻辑处理函数里的各种方法与属性
Django框架,Views(视图函数),也就是逻辑处理函数里的各种方法与属性 Views(视图函数)逻辑处理,最终是围绕着两个对象实现的 http请求中产生两个核心对象: http请求:HttpRe ...
- IOC关注服务(或应用程序部件)是如何定义的以及他们应该如何定位他们依赖的其它服务
IOC关注服务(或应用程序部件)是如何定义的以及他们应该如何定位他们依赖的其它服务.通常,通过一个容器或定位框架来获得定义和定位的分离,容器或定位框架负责: 保存可用服务的集合 提供一种方式将各种部件 ...
- 有一个TIME的类要求输出分和秒的值
#include <iostream> /* run this program using the console pauser or add your own getch, system ...
- linux -- Ubuntu 安装搜狗输入法
在Ubuntu Kylin系统中,默认安装搜狗拼音输入法,但是在原生Ubuntu系统中则不是.这可以理解,毕竟搜狗输入法的Linux版有Kylin团队的不小功劳.由于搜狗输入法确实比Linux系统下其 ...
- C++測量一段代码的执行时时间
在电脑里发现的一段C++代码,尽管自己不做C++开发了.还是贴出来,给须要的人 LARGE_INTEGER BegainTime ; LARGE_INTEGER EndTime ; LARGE_INT ...
- WF4.0——升级技能:泛型应用
前提: 在项目的开发中.我们知道,增加泛型,通过对类型的封装,进行抽象后.能够大大降低我们代码量,在项目中,泛型能够说是高级project师必备的技能之中的一个.也是面向对象的核心"抽象&q ...