Problem

Description

阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有 \(28\) 个按键,分别印有 \(26\) 个小写英文字母和 BP 两个字母。 经阿狸研究发现,这个打字机是这样工作的:

  • 输入小写字母,打字机的一个凹槽中会加入这个字母(按 P 前凹槽中至少有一个字母)。
  • 按一下印有 B 的按键,打字机凹槽中最后一个字母会消失。
  • 按一下印有 P 的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失(保证凹槽中至少有一个字母)。

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

a
aa
ab

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

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

Input Format

输入的第一行包含一个字符串,按阿狸的输入顺序给出所有阿狸输入的字符。

第二行包含一个整数 \(m\) ,表示询问个数。 接下来 \(m\) 行描述所有由小键盘输入的询问。其中第i行包含两个整数 \(x, y\) ,表示第i个询问为 \((x, y)\) 。

Output Format

输出 \(m\) 行,其中第 \(i\) 行包含一个整数,表示第 \(i\) 个询问的答案。

Sample

Input

aPaPBbP
3
1 2
1 3
2 3

Output

2
1
0

Range

所有测试数据的范围和特点如下表所示:

测试点编号 \(n\) 的规模 \(m\) 的规模 字符串长度 输入总长 (输入文件第一行的字符数)
1 \(1\le n \le 100\) \(1\le m \le 1000\) - \(\le 100\)
2 \(1\le n \le 100\) \(1\le m \le 1000\) - \(\le 100\)
3 \(1\le n \le 1000\) \(1\le m \le 10^4\) 单个长度 \(\le 1000\) ,总长度 \(\le 10^5\) \(\le 10^5\)
4 \(1\le n \le 1000\) \(1\le m \le 10^4\) 单个长度 \(\le 1000\) ,总长度 \(\le 10^5\) \(\le 10^5\)
5 \(1\le n \le 10^4\) \(1\le m \le 10^5\) 总长度 \(\le 10^5\) \(\le 10^5\)
6 \(1\le n \le 10^4\) \(1\le m \le 10^5\) 总长度 \(\le 10^5\) \(\le 10^5\)
7 \(1\le n \le 10^4\) \(1\le m \le 10^5\) 总长度 \(\le 10^5\) \(\le 10^5\)
8 \(1\le n \le 10^5\) \(1\le m \le 10^5\) - \(\le 10^5\)
9 \(1\le n \le 10^5\) \(1\le m \le 10^5\) - \(\le 10^5\)
10 \(1\le n \le 10^5\) \(1\le m \le 10^5\) - \(\le 10^5\)

Algorithm

\(Trie\) 图,树状数组

Mentality

欲得正解,先想暴力。

思考一下暴力怎么做:建 \(Trie\) -> 建 \(fail\) -> 对于每对询问 \(x,y\),由于 \(y\) 已经插入了 \(Trie\) 里,直接从 \(y\) 对应的终止结点往根走,并在每个点跳 \(fail\) 来计算答案。

当然,为了后面的分数,建 \(Trie\) 的时侯我们还是不能这么裸的,应该利用不同字符串间的高度重复性。由于打字机必需从末尾一个个删除字母,我们可以维护一个指针 \(pos\) 指向上个字符串的结尾,每当运行一次删除操作且此次操作会导致当前字符串与上一字符串的前缀重合长度 \(-1\) ,就令 \(pos\) 跳至父亲结点处。当执行打印命令时,则从 \(pos\) 处开始建 \(Trie\) 即可。

不难知道,这样我们的得分是 \(40\) 。

接下来,我们发现,\(fail\) 指针是构成了一棵树的,那么我们本来对于询问的暴力跳跃就可以得到转化:在 \(x\) 的 \(fail\) 边子树内,有多少个结点属于 \(y\) ?

那接下来就很明显了:

  • 先预处理出 \(fail\) 树上的 \(dfn\) 序,然后离线询问,将询问挂载在 \(y\) 的终止结点上。

  • 然后对 \(Trie\) 做 \(dfs\) ,每当进入一个结点就在对应的 \(fail\) 树上的 \(dfn\) 序位置 \(+1\) ,当退出一个结点就 \(-1\) 。这样一来,每当到达一个结点,就有且仅有根结点到当前结点路径上的点对应位置 \(+1\) ,满足了询问的统计条件。

  • 每到一个询问点 \(x\) ,查询 \([dfn[x],dfn[x]+size[x]-1]\) 区间内的 \(1\) 的个数即为答案。

Code

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
void read(int &x) {
x = 0;
char ch = getchar();
while (!isdigit(ch)) ch = getchar();
while (isdigit(ch)) {
x = x * 10 + ch - '0';
ch = getchar();
}
}
const int Max_n = 1e5 + 1, Max_m = 1e5 + 1, Max_len = 1e5 + 1, M = 26;
int n, m, Ans[Max_n];
int top, pos, cnt_T, End[Max_n];
char S[Max_len], now_S[Max_len];
vector<int> que_d[Max_len], que_x[Max_len];
queue<int> q;
void Trie_add(int x);
struct Trie {
int sur, ch[M], fa, deep, End;
int nx(int x, int now) {
if (!ch[x]) {
ch[x] = ++cnt_T;
Trie_add(now);
}
return ch[x];
}
} k[Max_len], tp[Max_len];
void Trie_add(int x) { k[cnt_T].fa = x, k[cnt_T].deep = k[x].deep + 1; }
void Trie_build() {
for (int i = 0; i < M; i++)
if (k[0].ch[i]) q.push(k[0].ch[i]);
while (!q.empty()) {
int x = q.front();
q.pop();
for (int i = 0; i < M; i++)
if (k[x].ch[i])
k[k[x].ch[i]].sur = k[k[x].sur].ch[i], q.push(k[x].ch[i]);
else
k[x].ch[i] = k[k[x].sur].ch[i];
}
}
int cntr, head[Max_len], nx[Max_len], to[Max_len];
int cntd, d[Max_len], size[Max_len];
int cnts, s[Max_len << 1];
int c[Max_len];
bool vis[Max_len];
void get_s(int x) {
s[++cnts] = x;
for (int i = 0; i < M; i++)
if (k[x].ch[i]) get_s(k[x].ch[i]);
s[++cnts] = x;
}
void addr(int u, int v) {
cntr++;
to[cntr] = v, nx[cntr] = head[u];
head[u] = cntr;
}
void Tree_build(int x) {
size[x] = 1, d[x] = ++cntd;
for (int i = head[x]; i; i = nx[i]) Tree_build(to[i]), size[x] += size[to[i]];
}
void c_add(int k, int x) {
if (!k) return;
for (int i = k; i <= cnt_T + 1; i += i & -i) c[i] += x;
}
int c_query(int k) {
int ans = 0;
for (int i = k; i; i -= i & -i) ans += c[i];
return ans;
}
void Ans_Count(int x) {
for (int i = 1; i <= cnts; i++) {
int x = s[i];
if (!vis[x]) { // 第一次访问代表进入结点
vis[x] = 1, c_add(d[x], 1);
for (int j = que_d[x].size() - 1; ~j; j--) {
int now = que_x[x][j];
Ans[que_d[x][j]] =
c_query(d[now] + size[now] - 1) - c_query(d[now] - 1);
}
} else // 第二次访问代表退出结点
c_add(d[x], -1);
}
}
int main() {
scanf("%s", S);
for (int i = 0, lim = strlen(S); i < lim; i++) {
if (S[i] == 'B')
top--, pos = k[pos].fa; // pos 的处理
else if (S[i] == 'P') {
for (int p = k[pos].deep + 1; p <= top; p++) // pos 的使用
pos = k[pos].nx(now_S[p] - 'a', pos);
k[pos].End = ++n;
End[n] = pos;
} else
now_S[++top] = S[i];
}
get_s(0); // 先处理出 Trie 树上点的访问顺序
Trie_build(); // fail 指针构建
read(m);
int x, y;
for (int i = 1; i <= m; i++) {
read(x), read(y);
que_d[End[y]].push_back(i);
que_x[End[y]].push_back(End[x]); // 挂载询问
}
for (int i = 1; i <= cnt_T; i++) addr(k[i].sur, i); // 连接 fail 边
Tree_build(0); // 构建 fail 树
Ans_Count(0); // 统计答案
for (int i = 1; i <= m; i++) printf("%d\n", Ans[i]);
}

【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. NOI 2011 【阿狸的打字机】

    之前讲了[AC自动姬],今天我终于把这题给刚下来了...嗯,来给大家讲一讲. 题目描述: 打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母.经阿狸研究发现,这个打字机是这样工 ...

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

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

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

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

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

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

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

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

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

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

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

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

随机推荐

  1. tp5.1 无限极分类前台展示

    商城前台的多级分类展示,如图所示,一般是三级.所以代码只组装到第三级. 数据库设计,共5个字段.level字段是方便用来显示级别的.pid是父级分类id,pid等于0说明它是一级分类.path字段,举 ...

  2. Windows Redis 安装(带视频)

    疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] 疯狂创客圈 高并 ...

  3. 安全性测试:OWASP ZAP 2.8 使用指南(四):ZAP扫描移动应用安全

    在做移动应用(APP,小程序等)测试时,需要关注应用安全性. ZAP是可以用来进行手机移动应用渗透性测试扫描的. 正因为ZAP是采用“中间代理”的形式,截取并扫描所有客户端与服务器的交互请求,作为客户 ...

  4. Dubbo学习系列之十(Sentinel之限流与降级)

    各位看官,先提个问题,如果让你设计一套秒杀系统,核心要点是啥???我认为有三点:缓存.限流和分离.想当年12306大面积崩溃,还有如今的微博整体宕机情况,感觉就是限流降级没做好,"用有限的资 ...

  5. 操作MyBatis引发Error setting null for parameter #X with JdbcType OTHER .无效的列类型

    再用MyBatis操作Oracle的时候,传入null值而引发的错误 异常信息: org.springframework.jdbc.UncategorizedSQLException: Error s ...

  6. Java并发J.U.C学习总结

    转载自http://www.cnblogs.com/chenpi/结合自己理解稍有添加自己的理解 阅读目录 JSR 166及J.U.C Executor框架(线程池. Callable .Future ...

  7. IDEA中使用Maven模板创建Servelet项目并使用Tomcat来运行项目

    首先需要正确安装Maven和Tomact,Maven安装和Tomact安装步骤,参见别的文章. 一.创建Maven工作空间 点击Finish按钮后,耐心等待.直到出现BUILD SUCCESS为止. ...

  8. Java学习 1.1——(JVM介绍)Java为什么能够跨平台?

    首先介绍一下Java的各个层级,先放一张图: 硬件,操作系统和操作系统接口:这三级不说大家都知道,操作系统有很多种,比如Windows,Linux.Windows又分为win7,win10,win x ...

  9. Apply Grouping to List View Data 将分组应用于列表视图数据

    This lesson will teach you how to apply grouping to List View data. For this purpose, you will group ...

  10. PHP服务化搭建之nginx动静分离实战

    如有什么问题可以加群交流:647617935 什么是动静分离 动静分离:将项目中的CSS,JS,HTML,JPG'.等静态资源和 PHP等动态资源分开处理的一种方式 动静分离优点 不同的文件由不同类型 ...