洛谷传送门
BZOJ传送门
题目背景
阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。
题目描述
打字机上只有 个按键,分别印有 个小写英文字母和 、 两个字母。经阿狸研究发现,这个打字机是这样工作的:
- 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。
- 按一下印有 的按键,打字机凹槽中最后一个字母会消失。
- 按一下印有 的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。
例如,阿狸输入aPaPBbP
,纸上被打印的字符如下:
a
aa
ab
我们把纸上打印出来的字符串从
开始顺序编号,一直到
。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数
(其中
),打字机会显示第
个打印的字符串在第
个打印的字符串中出现了多少次。
阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?
输入输出格式
输入格式:
输入的第一行包含一个字符串,按阿狸的输入顺序给出所有阿狸输入的字符。
第二行包含一个整数 ,表示询问个数。
接下来 行描述所有由小键盘输入的询问。其中第 行包含两个整数 ,表示第 个询问为 。
输出格式:
输出 行,其中第i行包含一个整数,表示第i个询问的答案。
输入输出样例
输入样例#1:
aPaPBbP
3
1 2
1 3
2 3
输出样例#1:
2
1
0
说明
数据范围:
对于 的数据, ,第一行总长度 。
解题分析
自动机好题。 建自动机什么的都好说, 只是额外维护一个父亲节点表示从其转移过来的即可完成回退操作。
问题在于如果我们每次暴力匹配复杂度会变成 的。 如何优化?
考虑我们是如何暴力的: 对 串建立 自动机, 将 串依次插入, 暴力跳 , 看是否能到达 串的终止位置。 我们发现, 只要插入的字符在 树上在 串终止位置的子树中, 就会产生贡献。
这样就好做了:我们离线询问, 整颗 树, 每到达一个节点就将其在 树上的 序处的贡献 。 如果到达了某个串的终止节点, 就回答其作为 串的所有询问(直接查询 在 树中的子树贡献之和)。
代码如下:
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <queue>
#include <vector>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define lbt(i) ((i) & (-(i)))
#define MX 100500
template <class T>
IN void in(T &x)
{
x = 0; R char c = gc;
for (; !isdigit(c); c = gc);
for (; isdigit(c); c = gc)
x = (x << 1) + (x << 3) + c - 48;
}
int cnt, m, tot, dfn, root;
int son[MX][26], fat[MX], fail[MX], ed[MX], lb[MX], rb[MX], head[MX], tree[MX], p[MX], ans[MX];
char buf[MX];
struct INFO {int tar, tim;};
std::vector <int> to[MX];
std::vector <INFO> que[MX];
std::queue <int> q;
IN void add(R int pos, R int del) {for (; pos <= cnt + 1; pos += lbt(pos)) tree[pos] += del;}
IN int query(R int pos) {int ret = 0; for (; pos; pos -= lbt(pos)) ret += tree[pos]; return ret;}
IN void insert(char *str)
{
R int now = root, len = std::strlen(str), id;
for (R int i = 0; i < len; ++i)
{
if (str[i] == 'P') ed[now] = ++tot, p[tot] = now;
else if (str[i] == 'B') now = fat[now];
else
{
id = str[i] - 'a';
if (!son[now][id]) son[now][id] = ++cnt, fat[son[now][id]] = now;
now = son[now][id];
}
}
}
void build()
{
R int now, cur; fail[0] = -1;
for (R int i = 0; i < 26; ++i) if (son[root][i]) q.push(son[root][i]), to[root].push_back(son[root][i]);
W (!q.empty())
{
now = q.front(); q.pop();
for (R int i = 0; i < 26; ++i)
{
if (son[now][i])
{
cur = fail[now];
W ((~fail[cur]) && (!son[cur][i])) cur = fail[cur];
fail[son[now][i]] = son[cur][i];
q.push(son[now][i]); to[fail[son[now][i]]].push_back(son[now][i]);
}
}
}
}
void DFS1(R int now)
{
lb[now] = ++dfn;
for (R int i = to[now].size() - 1; ~i; --i) DFS1(to[now][i]);
rb[now] = dfn;
}
void DFS2(R int now)
{
add(lb[now], 1);
if (ed[now])
{
INFO cur;
for (R int i = que[now].size() - 1; ~i; --i)
{
cur = que[now][i];
ans[cur.tim] = query(rb[p[cur.tar]]) - query(lb[p[cur.tar]] - 1);
}
}
for (R int i = 0; i < 26; ++i)
if (son[now][i]) DFS2(son[now][i]);
add(lb[now], -1);
}
int main(void)
{
int x, y;
scanf("%s", buf); insert(buf);
in(m);
for (R int i = 1; i <= m; ++i)
{
in(x), in(y);
que[p[y]].push_back({x, i});
}
build(); DFS1(0); DFS2(0);
for (R int i = 1; i <= m; ++i) printf("%d\n", ans[i]);
}