题意
阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有28个按键,分别印有26个小写英文字母和'B'、'P'两个字母。
经阿狸研究发现,这个打字机是这样工作的:
输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。
按一下印有'B'的按键,打字机凹槽中最后一个字母会消失。
按一下印有'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 个询问的答案。
输入输出样例
输入 #1
aPaPBbP
3
1 2
1 3
2 3
输出 #1
2
1
0
分析
感觉自己字符串题只会依赖哈希很丢人,于是去学了 KMP 和 AC自动机,然后开了这题。。
现在假设我们建完了 AC自动机,得到了 图。
首先我们看一下 是 的子串的条件是什么,其实是存在 的某个节点,它沿着 指针一直跳,能跳到 的末节点(其实就是 是 某个前缀的后缀)。 在 中的出现次数,其实就是 的所有节点中,以 为后缀的节点数,这些节点都可以通过跳 跳到 的末节点。
不妨把 指针和 树的所有节点看成一颗树(这棵树叫树),那么所有能跳到 的点都在 的子树中。
现在的问题就转化为: 的子树中,有多少个点属于 。
我们把属于 的点记为 ,那么答案就是 的子树和。
子树和,其实就是 序上的前缀和。
然后考虑建 树的过程:
- 相当于跳到父节点
- 相当于新打印出一个字符串
- 其他就是正常建
这样子, 的形态一直都是一条链。
我们考虑离线,求 作为询问串时 的出现次数。
那么我们在搞 的时候:
- 遇到 ,在 处 ,然后
- 遇到 ,说明此时节点为一个字符串 的结束,处理所有询问
- 对于其他,在 处
这样对于每一个询问 ,我们只用看 的子树和,然后用树状数组可以快速维护前缀和。
复杂度 。
代码如下
#include <bits/stdc++.h> #include<ext/pb_ds/hash_policy.hpp> #include<ext/pb_ds/assoc_container.hpp> #define m_p make_pair #define N 100005 using namespace __gnu_pbds; using namespace std; typedef long long LL; typedef unsigned long long uLL; struct custom_hash { static uint64_t splitmix64(uint64_t x) { x += 0x9e3779b97f4a7c15; x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9; x = (x ^ (x >> 27)) * 0x94d049bb133111eb; return x ^ (x >> 31); } size_t operator()(uint64_t x) const { static const uint64_t FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count(); return splitmix64(x + FIXED_RANDOM); } }; LL z = 1; int read(){ int x, f = 1; char ch; while(ch = getchar(), ch < '0' || ch > '9') if(ch == '-') f = -1; x = ch - '0'; while(ch = getchar(), ch >= '0' && ch <= '9') x = x * 10 + ch - 48; return x * f; } int ksm(int a, int b, int p){ int s = 1; while(b){ if(b & 1) s = z * s * a % p; a = z * a * a % p; b >>= 1; } return s; } int ch[N][26], Q[N], h[N], fa[N], v[N], c[N], fail[N], l[N], r[N], ans[N], dfn, cnt, tot, ret; char s[N]; vector<pair<int, int> > q[N]; struct node{ int a, b, n; }d[N * 2]; void add(int i, int x){ for(; i <= 100000; i += i&-i) c[i] += x; } int query(int i){ int s = 0; for(; i > 0; i -= i&-i) s += c[i]; return s; } void cr(int a, int b){ d[++cnt].a = a; d[cnt].b = b; d[cnt].n = h[a]; h[a] = cnt; } void insert(char *s){ int i, c, n = strlen(s), p = 0; for(i = 0; i < n; i++){ if(s[i] == 'P') Q[++ret] = p; else if(s[i] == 'B') p = fa[p]; else{ c = s[i] - 'a'; if(!ch[p][c]) ch[p][c] = ++tot, fa[tot] = p; p = ch[p][c]; } } } void get_fail(){ queue<int> q; int i, a, j; for(i = 0; i < 26; i++) if(ch[0][i]) q.push(ch[0][i]); while(!q.empty()){ a = q.front(); q.pop(); for(i = 0; i < 26; i++){ if(ch[a][i]) fail[ch[a][i]] = ch[fail[a]][i], q.push(ch[a][i]); else ch[a][i] = ch[fail[a]][i]; } } } void dfs(int a, int f){ int i, b; l[a] = ++dfn; for(i = h[a]; i; i = d[i].n){ b = d[i].b; if(b == f) continue; dfs(b, a); } r[a] = dfn; } void work(char *s){ int i, j, c, n, p = 0, ret = 0, x, id; n = strlen(s); for(i = 0; i < n; i++){ if(s[i] == 'P'){ ret++; for(auto t: q[ret]){ x = Q[t.first]; id = t.second; ans[id] = query(r[x]) - query(l[x] - 1); } } else if(s[i] == 'B') add(l[p], -1), p = fa[p]; else{ c = s[i] - 'a'; if(!ch[p][c]) ch[p][c] = ++tot, fa[tot] = p; p = ch[p][c]; add(l[p], 1); } } } int main(){ int i, j, n, m, x, y; scanf("%s", s); insert(s); get_fail(); for(i = 1; i <= tot; i++) cr(fail[i], i); dfs(0, 0); m = read(); for(i = 1; i <= m; i++){ x = read(); y = read(); q[y].push_back(m_p(x, i)); } memset(ch, 0, sizeof(ch)); memset(fa, 0, sizeof(fa)); tot = 0; work(s); for(i = 1; i <= m; i++) printf("%d\n", ans[i]); return 0; }