★【AC自动机】【树状数组】【NOI2011】阿狸的打字机

【问题描述】
阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机
上只有 28 个按键,分别印有 26 个小写英文字母和'B'、'P'两个字母。
经阿狸研究发现,这个打字机是这样工作的:
输入小写字母,打字机的一个凹槽中会加入这个字母(按 P 前凹槽中至少有一个字母)。
按一下印有'B'的按键,打字机凹槽中最后一个字母会消失。
按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失(保证凹槽中至少有一个字母)
例如,阿狸输入 aPaPBbP,纸上被打印的字符如下:
a
aa
ab
我们把纸上打印出来的字符串从 1 开始顺序编号,一直到 n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数(x,y)(其中 1≤x,y≤n),打字机会显示第 x 个打印的字符串在第 y 个打印的字符串中出现了多少次。阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?
【输入格式】
从文件 type.in 中读入数据。
输入的第一行包含一个字符串,按阿狸的输入顺序给出所有阿狸输入的字符。
第二行包含一个整数 m,表示询问个数。
接下来 m 行描述所有由小键盘输入的询问。其中第 i 行包含两个整数 x, y,表示第 i 个询问为(x, y)。
【输出格式】
输出到文件 type.out 中。
输出 m 行,其中第 i 行包含一个整数,表示第 i 个询问的答案。

【样例输入】
aPaPBbP
3
1 2
1 3
2 3
【样例输出】
2
1
0

【数据规模与约定】
所有测试数据的范围和特点如下表所示


此题考察AC自动机及树状数组的应用。
首先考虑朴素的做法。
首先将所有单词建立一棵Trie树,然后对于每个询问(x, y),在y对应的单词路径上的每一个点都沿着Fail指针寻找,若找到x则加一,最后得到的结果就是该询问的结果。
这样做大概能得30分。

一种优化:用离线算法,一次处理多个相关的询问,大概能得70分。
于是还要继续优化。

通过观察可以发现:
若将Fail指针反向,则整个图又构成一颗树(称之为Fail树),这样每次处理询问(x, y)时就一定是在Fail树中以x为根的子树中找y这条单词链的个数。
通过求Dfs序,可以将这个问题转化为求区间和的问题,于是树状数组便派上了用场。

具体方法:
在处理询问时,重新遍历一次最开始读入的字符串,遇到一个小写字母就入栈并将该字母对应在树状数组中的位置加一,遇到一个P就处理询问,遇到一个B就出栈并将出栈的字母对应则树状数组的位置减一。

Accode:

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <string>
const int maxN = 100010, SIZE = 0xfffff;

struct Node
{
	Node *next[26], *Fail, *pre;
	Node(): Fail(NULL), pre(NULL)
	{memset(next, 0, sizeof next);}
} trienode[maxN], *root, *q[SIZE + 1];
//trienode保存所有节点,方便使用指针减法快速映射。
struct Edge
{
	int v; Edge *next; Edge() {}
	Edge(int v, Edge *next): v(v), next(next) {}
} *edge[maxN];
struct Ask
{
	int x, ord; Ask *next; Ask() {}
	Ask(int x, int ord, Ask *next): 
		x(x), ord(ord), next(next) {}
} *ask[maxN]; char str[maxN];
int DFN[maxN], size[maxN], a[maxN];
int word[maxN], ans[maxN], n, m, tot;

inline void Ins(int u, int v)
{edge[u] = new Edge(v, edge[u]); return;}

void Dfs(int u)
{
	static int tot = 0;
	DFN[u] = tot++; size[u] = 1; //这里size初始化为1。
	for (Edge *p = edge[u]; p; p = p -> next)
	if (!DFN[p -> v]) 
	{Dfs(p -> v); size[u] += size[p -> v];} 
	//注意维护各个子树大小的方法。
	return;
}

inline void Add(int x, int Delta)
{
	for (int i = x; i <= tot; i += (i & -i))
		a[i] += Delta;
	return;
}

inline int sum(int x)
{
	int sum = 0;
	for (int i = x; i; i -= (i & -i)) sum += a[i];
	return sum;
}

int main()
{
	freopen("type.in", "r", stdin);
	freopen("type.out", "w", stdout);
	scanf("%s", str);
	Node *p = root = &trienode[tot++]; //这里tot一定要自加一。
	root -> pre = NULL;
	for (int i = 0; str[i]; ++i)
	{
		if (str[i] == 'P') word[++n] = tot - 1;
		else if (str[i] == 'B') p = p -> pre;
		else 
		{
			if (!(p -> next[str[i] - 'a']))
			{
				p -> next[str[i] - 'a'] = &trienode[tot++];
				p -> next[str[i] - 'a'] -> pre = p;
			}
			p = p -> next[str[i] - 'a'];
		}
	}
	root -> Fail = NULL; int f = 0, r = 0;
	for (q[r++] = root; f - r;)
	{
		Node *Now = q[f++], *p = NULL; f &= SIZE;
		for (int i = 0; i < 26; ++i) if (Now -> next[i])
		{
			for (p = Now -> Fail; p; p = p -> Fail)
			if (p -> next[i])
			{
				Now -> next[i] -> Fail = p -> next[i];
				Ins(p -> next[i] - root, Now -> next[i] - root);
				break;
			}
			if (!p)
			{
				Now -> next[i] -> Fail = root;
				Ins(0, Now -> next[i] - root);
			}
			q[r++] = Now -> next[i], r &= SIZE;
		}
	}
	Dfs(0); scanf("%d", &m);
	for (int i = 0; i < m; ++i)
	{
		int x, y; scanf("%d%d", &x, &y);
		ask[y] = new Ask(x, i, ask[y]);
	}
	n = 0; p = root;
	for (int i = 0; str[i]; ++i)
	{
		if (str[i] == 'P')
		for (Ask *tmp = ask[++n]; tmp; tmp = tmp -> next)
			ans[tmp -> ord]
				= sum(DFN[word[tmp -> x]]
				+ size[word[tmp -> x]] - 1)
				- sum(DFN[word[tmp -> x]] - 1);
		//注意这里要减一。
		else if (str[i] == 'B')
		{
			Add(DFN[p - root], -1);
			p = p -> pre;
		}
		else
		{
			p = p -> next[str[i] - 'a'];
			Add(DFN[p - root], 1);
		}
	}
	for (int i = 0; i < m; ++i) printf("%d\n", ans[i]);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值