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

来源:互联网 发布:大数据现状 编辑:程序博客网 时间:2024/06/05 01:34

【问题描述】阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有 28 个按键,分别印有 26 个小写英文字母和'B'、'P'两个字母。经阿狸研究发现,这个打字机是这样工作的:输入小写字母,打字机的一个凹槽中会加入这个字母(按 P 前凹槽中至少有一个字母)。按一下印有'B'的按键,打字机凹槽中最后一个字母会消失。按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失(保证凹槽中至少有一个字母)例如,阿狸输入 aPaPBbP,纸上被打印的字符如下:aaaab我们把纸上打印出来的字符串从 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 个询问的答案。【样例输入】aPaPBbP31 21 32 3【样例输出】210【数据规模与约定】所有测试数据的范围和特点如下表所示


此题考察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;}

原创粉丝点击