NOI2011阿狸的打字机(fail树+DFS序)
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
3
1 2
1 3
2 3
Sample Output
1
0
HINT
1<=N<=10^5
fail树是这样一个东西:将AC自动机的fail指针连成一棵树(根节点为虚拟节点0)。
这样有什么好处呢?匹配时沿着fail树向上找就能找到所有匹配位置了:
void find(char* s)
{
int j=,c;
for(int i=;s[i]!='\0';i++)
{
c=s[i]-'a';
while(j&&!ch[j][c]) j=f[j];
for(int t=j;t;t=f[t]) if(val[t]) cnt[val[t]]++;
}
}
也就是说,对于一个节点c从root(0)走向c连成的字符串(模板串),一个节点v从root(0)走向v连成的字符串(匹配串),匹配串在模板串中出现的次数就是root->c的路径上的节点有多少出现在节点v的子树中。
这样我们只需维护DFS序即可。
那么这道题的做法就水落石出了:
首先构建AC自动机的fail树得出dfs序,得出每个结点进出时间l[x],r[x],考虑这样一种暴力
对于一个询问x,y,查询自动机上root-y的每一个结点,沿着fail指针是否会走到x的结尾点,如果可以即答案+1
那么就变为在fail树中,查询自动机上root-y的所有结点中,有多少个在x的子树中。
只要在自动机上再重新走一遍,走到一个结点y,则将1-l[y]都+1。解决询问x,y(把y相同的链表串起来),即查询l[x]到r[x]的和。当遇到一个B时1-l[y]都-1。
只需树状数组实现加减和区间求和
随便写了写,竟然在BZOJ上rank1了,我很开心
#include<cstdio>
#include<cctype>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
inline int read()
{
int x=,f=;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-;
for(;isdigit(c);c=getchar()) x=x*+c-'';
return x*f;
}
void print(int x)
{
if(!x) {putchar('');putchar('\n');return;}
int len=,buf[];
while(x) buf[++len]=x%,x/=;
for(int i=len;i;i--) putchar(buf[i]+'');
putchar('\n');
}
const int maxn=;
int ch[maxn][],fa[maxn],f[maxn],pos[maxn],id,sz;
void insert(char* s)
{
int j=,c;
for(int i=;s[i];i++)
{
c=s[i]-'a';
if(s[i]=='P') pos[++id]=j;
else if(s[i]=='B') j=fa[j];
else
{
if(!ch[j][c]) ch[j][c]=++sz,fa[sz]=j;
j=ch[j][c];
}
}
}
queue<int> q;
void getfail()
{
for(int c=;c<;c++) if(ch[][c]) q.push(ch[][c]);
while(!q.empty())
{
int u=q.front(),v;q.pop();
for(int c=;c<;c++) if(v=ch[u][c])
{
q.push(v);int j=f[u];
while(j&&!ch[j][c]) j=f[j];
f[v]=ch[j][c];
}
}
}
int first[maxn],next[maxn],to[maxn],e;
void AddEdge(int u,int v){to[++e]=v;next[e]=first[u];first[u]=e;}
int l[maxn],r[maxn],cnt;
void dfs(int x)
{
l[x]=++cnt;
for(int i=first[x];i;i=next[i]) dfs(to[i]);
r[x]=++cnt;
}
int sumv[maxn];
void add(int x,int v) {for(;x<=cnt;x+=x&-x) sumv[x]+=v;}
int sum(int x) {int ret=;for(;x;x-=x&-x) ret+=sumv[x];return ret;}
struct Query {int next,v;}Q[maxn];
int firstq[maxn],ans[maxn];
void solve(char* s)
{
int j=,c;add(l[j],);id=;
for(int i=;s[i];i++)
{
c=s[i]-'a';
if(s[i]=='P')
{
id++;for(int x=firstq[id];x;x=Q[x].next)
{
int v=pos[Q[x].v];
ans[x]=sum(r[v])-sum(l[v]-);
}
}
else if(s[i]=='B') add(l[j],-),j=fa[j];
else j=ch[j][c],add(l[j],);
}
}
char s[maxn];
int main()
{
scanf("%s",s);insert(s);getfail();
for(int i=;i<=sz;i++) AddEdge(f[i],i);
dfs();
int m=read();
for(int i=;i<=m;i++)
{
int x=read(),y=read();
Q[i].v=x;Q[i].next=firstq[y];firstq[y]=i;
}
solve(s);
for(int i=;i<=m;i++) print(ans[i]);
return ;
}
NOI2011阿狸的打字机(fail树+DFS序)的更多相关文章
- BZOJ 2434: [Noi2011]阿狸的打字机( AC自动机 + DFS序 + 树状数组 )
一个串a在b中出现, 那么a是b的某些前缀的后缀, 所以搞出AC自动机, 按fail反向建树, 然后查询(x, y)就是y的子树中有多少是x的前缀. 离线, 对AC自动机DFS一遍, 用dfs序+树状 ...
- 【BZOJ2434】[NOI2011]阿狸的打字机 AC自动机+DFS序+树状数组
[BZOJ2434][NOI2011]阿狸的打字机 Description 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P ...
- BZOJ2434[Noi2011]阿狸的打字机——AC自动机+dfs序+树状数组
题目描述 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母. 经阿狸研究发现,这个打字机是这样工作的: l 输入小 ...
- 【BZOJ 2434】 [Noi2011]阿狸的打字机 fail树+树状数组
就是考了一个fail树的神奇应用我们建出fail树之后,发现我们就是在求y到根的路径上所有的点在以x为根的子树里的个数,这个我们离线后用树状数组+dfs序即可解决 #include <cstdi ...
- NOI 2011 阿狸的打字机 (AC自动机+dfs序+树状数组)
题目大意:略(太长了不好描述) 良心LOJ传送门 先对所有被打印的字符串建一颗Trie树 观察数据范围,并不能每次打印都从头到尾暴力建树,而是每遍历到一个字符就在Trie上插入这个字符,然后记录每次打 ...
- [NOI2011][bzoj2434] 阿狸的打字机 [AC自动机+dfs序+fail树+树状数组]
题面 传送门 正文 最暴力的 最暴力的方法:把所有询问代表的字符串跑一遍kmp然后输出 稍微优化一下:把所有询问保存起来,把模板串相同的合并,求出next然后匹配 但是这两种方法本质没有区别,都是暴力 ...
- 【BZOJ-2434】阿狸的打字机 AC自动机 + Fail树 + DFS序 + 树状数组
2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 2022 Solved: 1158[Submit][Sta ...
- BZOJ2434 [Noi2011]阿狸的打字机(AC自动机 + fail树 + DFS序 + 线段树)
题目这么说的: 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母.经阿狸研究发现,这个打字机是这样工作的: 输入小 ...
- BZOJ2434: [Noi2011]阿狸的打字机(fail树+dfs序)
Description 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母. 经阿狸研究发现,这个打字机是这样工作的 ...
随机推荐
- 【USACO】numtri
给一颗数字树,让找一条数字和最大的路径.一下子就想起刚学不久的回溯法了.照着写了个代码,调了调搞通了.在小数据的情况下是对的,但是在test 6 树有199层的时候溢出了. #include < ...
- docke跨主机通信之gre隧道
GRE简介 GRE可以对网络层的任何协议来进行封装,类似LVS的IPIP协议,在原有的数据报上增加GRE协议数据报.然后在网络上传输,到达对端后,解开GRE数据报头,得到真实的数据报.其中的mac地址 ...
- 浅谈Java的输入输出流(转)
Java语言的输入输出功能是十分强大而灵活的,美中不足的是看上去输入输出的代码并不是很简洁,因为你往往需要包装许多不同的对象.在Java类库中,IO部分的内容是很庞大的,因为它涉及的领域很广泛:标准输 ...
- 发现磁盘的shell
#!/bin/bash #set -x diskarray=(`cat /proc/diskstats |grep -E "\bsd[a-z]\b|\bxvd[a-z]\b|\bvd[a-z ...
- js “+” 连接字符串&数字相加 数字相加出现多位小数 函数调用单引号双引号嵌套和转义字符的使用
一.机制 JavaScript中,加号不仅表示相加还表示字符串连接 当加号两边存在字符串时,加号代表连接,实际上是将两侧都转为了字符串,如 "1" + 1 = "11&q ...
- Android中获取蓝牙log
1.蓝牙的snoop log存放位置 /etc/bluetooth/bt_stack.conf 2.修改方法 #关闭蓝牙 修改bt_stack.conf文件中打印log的等级 adb root a ...
- C#实现UTC时间与Datetime转换
为了便于传输,通信过程中传输的都是:当前时间跟标准时间相隔的秒数,并且是以16进制字节的形式传输的. public double ConvertDateTimeInt(System.DateTime ...
- Android 建立文件夹、生成文件并写入文本文件内容
一.首先添加权限 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE">& ...
- synchronized的理解
用法解释 synchronized是Java中的关键字,是一种同步锁.它修饰的对象有以下几种: 1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调 ...
- JVM的粗略简述
什么是Java虚拟机 虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的.Java虚拟机有自己完善的硬体架构,如处理器.堆栈.寄存器等,还具有相应的指令系统.JVM屏蔽了与 ...