[BZOJ2434][Noi2011]阿狸的打字机
[BZOJ2434][Noi2011]阿狸的打字机
试题描述
阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有28个按键,分别印有26个小写英文字母和'B'、'P'两个字母。
经阿狸研究发现,这个打字机是这样工作的:
l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。
l 按一下印有'B'的按键,打字机凹槽中最后一个字母会消失。
l 按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。
例如,阿狸输入aPaPBbP,纸上被打印的字符如下:
a
aa
ab
我们把纸上打印出来的字符串从1开始顺序编号,一直到n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数(x,y)(其中1≤x,y≤n),打字机会显示第x个打印的字符串在第y个打印的字符串中出现了多少次。
阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?
输入
输入的第一行包含一个字符串,按阿狸的输入顺序给出所有阿狸输入的字符。
第二行包含一个整数m,表示询问个数。
接下来m行描述所有由小键盘输入的询问。其中第i行包含两个整数x, y,表示第i个询问为(x, y)。
输出
输出m行,其中第i行包含一个整数,表示第i个询问的答案。
输入示例
aPaPBbP 3 1 2 1 3 2 3
输出示例
2 1 0
数据规模及约定
1<=N<=10^5
1<=M<=10^5
题解
先构建一个AC自动机,建立好 Trie 树和 Fail 树。
不难想到一个暴力:对于每个询问(x, y),从 Trie 树中 y - root 的每个节点出发顺着 Fail 边走,能够到达 x 结点的路径条数即为答案。
不妨换个角度思考:对于询问(x, y),在 Fail 树中,以节点 x 为根的子树中满足“条件”的节点个数。一个节点满足“条件”当且仅当该节点在 Trie 树中的 y - root 上。
既然是处理子树问题,就可以用dfs序将其转化为序列问题了。
求一遍 Fail 树的dfs序,重新遍历一遍AC自动机,遇到 Trie 树上的节点 u 时将dfs序上位于 L[u] 的位置加 1,若是从节点 u 向上退格则将位于 L[u] 的位置减 1. 离线处理每个询问,在当前结点为 y 的时候,把所有(xi, y)的询问处理掉,其答案为区间 [ L[xi], R[xi] ] 的和。
#include <iostream> #include <cstdio> #include <algorithm> #include <cmath> #include <stack> #include <vector> #include <queue> #include <cstring> #include <string> #include <map> #include <set> using namespace std; const int BufferSize = 1 << 16; char buffer[BufferSize], *Head, *tail; inline char Getchar() { if(Head == tail) { int l = fread(buffer, 1, BufferSize, stdin); tail = (Head = buffer) + l; } return *Head++; } int read() { int x = 0, f = 1; char c = Getchar(); while(!isdigit(c)){ if(c == '-') f = -1; c = Getchar(); } while(isdigit(c)){ x = x * 10 + c - '0'; c = Getchar(); } return x * f; } #define maxn 100010 #define maxs 26 int n, q, ans[maxn], pos[maxn], cnt; char S[maxn]; int m, head[maxn], next[maxn], to[maxn]; void AddEdge(int a, int b) { // printf("%d -> %d\n", a, b); to[++m] = b; next[m] = head[a]; head[a] = m; return ; } int ToT = 1, f[maxn], fa[maxn], ch[maxn][maxs], Q[maxn], hd, tl; void GetFail() { hd = tl = 0; f[1] = 1; for(int i = 0; i < maxs; i++) if(ch[1][i]) { int u = ch[1][i]; f[u] = 1; Q[++tl] = u; AddEdge(1, u); } while(hd < tl) { int r = Q[++hd]; for(int i = 0; i < maxs; i++) if(ch[r][i]) { int u = ch[r][i]; int j = f[r]; while(j > 1 && !ch[j][i]) j = f[j]; f[u] = ch[j][i] ? ch[j][i] : 1; AddEdge(f[u], u); Q[++tl] = u; } } return ; } int tot, L[maxn], R[maxn]; void build(int u) { L[u] = ++tot; for(int e = head[u]; e; e = next[e]) build(to[e]); R[u] = tot; return ; } int headq[maxn], nextq[maxn], qx[maxn]; int C[maxn]; void add(int x, int v) { for(; x <= tot; x += x & -x) C[x] += v; return ; } int sum(int x) { int res = 0; for(; x; x -= x & -x) res += C[x]; return res; } int main() { // freopen("data.in", "r", stdin); // freopen("data.out", "w", stdout); char tc = Getchar(); while(!isalpha(tc)) tc = Getchar(); int u = 1; while(isalpha(tc)) { if('a' <= tc && tc <= 'z') { int d = tc - 'a'; if(!ch[u][d]) ch[u][d] = ++ToT, fa[ch[u][d]] = u; u = ch[u][d]; } if(tc == 'P') pos[++cnt] = u; if(tc == 'B' && fa[u]) u = fa[u]; S[++n] = tc; tc = Getchar(); } // printf("%d\n", ToT); // for(int i = 1; i <= cnt; i++) printf("%d ", pos[i]); putchar('\n'); GetFail(); build(1); q = read(); for(int i = 1; i <= q; i++) { int x = pos[read()], y = pos[read()]; nextq[i] = headq[y]; headq[y] = i; qx[i] = x; } u = 1; for(int i = 1; i <= n; i++) { if('a' <= S[i] && S[i] <= 'z') u = ch[u][S[i]-'a'], add(L[u], 1); if(S[i] == 'B' && fa[u]) add(L[u], -1), u = fa[u]; if(S[i] == 'P') for(int i = headq[u]; i; i = nextq[i]) ans[i] = sum(R[qx[i]]) - sum(L[qx[i]]-1); } for(int i = 1; i <= q; i++) printf("%d\n", ans[i]); return 0; }