【问题描述】 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机 上只有 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; }