[Luogu P2414] [BZOJ 2434] [NOI2011]阿狸的打字机

版权声明:欢迎转载蒟蒻博客,但请注明出处: https://blog.csdn.net/LPA20020220/article/details/84037199

洛谷传送门

BZOJ传送门

题目背景

阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。

题目描述

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

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

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

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

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

输入输出格式

输入格式:

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

第二行包含一个整数 m m ,表示询问个数。

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

输出格式:

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

输入输出样例

输入样例#1:

aPaPBbP
3
1 2
1 3
2 3

输出样例#1:

2
1
0

说明

数据范围:

对于 100 % 100\% 的数据, n 100000 , m 100000 n\le 100000,m\le 100000 ,第一行总长度 100000 \le 100000

img

解题分析

A C AC 自动机好题。 建自动机什么的都好说, 只是额外维护一个父亲节点表示从其转移过来的即可完成回退操作。

问题在于如果我们每次暴力匹配复杂度会变成 O ( N 2 ) O(N^2) 的。 如何优化?

考虑我们是如何暴力的: 对 x x 串建立 A C AC 自动机, 将 y y 串依次插入, 暴力跳 f a i l fail , 看是否能到达 x x 串的终止位置。 我们发现, 只要插入的字符在 f a i l fail 树上在 x x 串终止位置的子树中, 就会产生贡献。

这样就好做了:我们离线询问, D F S DFS 整颗 t r i e trie 树, 每到达一个节点就将其在 f a i l fail 树上的 d f s dfs 序处的贡献 + 1 +1 。 如果到达了某个串的终止节点, 就回答其作为 y y 串的所有询问(直接查询 x x f a i l fail 树中的子树贡献之和)。

代码如下:

#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]);
}

猜你喜欢

转载自blog.csdn.net/LPA20020220/article/details/84037199