您的位置:首页 > 其它

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

2012-05-07 16:38 567 查看
【问题描述】
阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机
上只有 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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息