题目背景
阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。
题目描述
打字机上只有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
说明
对于100%的数据,n<=100000,m<=100000,第一行总长度<=100000。
思路与解
AC自动机+dfs序+树状数组。
由于涉及多个串,不难想到用AC自动机处理。考虑构建了Fail指针后形成的fail树。对于一个询问 (x,y),也就是在fail树中y的子树中寻找x的前缀。然而在fail树中维护每一个串的前缀是不可能的,因而只能反其道而行之。
在dfs遍历Trie的过程中维护当前链是容易的(即在一个树状数组中使得为1的元素是当前链上的)。当我们处理到节点y时,对于任何一个询问 (x,y),只需要查看x的子树中1的个数,后者可以用dfs序处理。复杂度为 O(LΣ+(L+N)lgL)。
Code
#include <bits/stdc++.h>
using namespace std;
char str[100005];
//#define Debug
struct graph {
struct node {
int to, next, id;
} edge[100005];
int head[100005], top;
int L[100005], R[100005], d;
graph():top(0) { memset(head, 0, sizeof head); d = 0; }
void push(int i, int j, int id = 0)
{
edge[++top].to = j;
edge[top].next = head[i];
edge[top].id = id;
head[i] = top;
}
void dfs(int nd = 1)
{
L[nd] = ++d;
for (int i = head[nd]; i; i = edge[i].next) dfs(edge[i].to);
R[nd] = d;
}
} query;
struct acm {
struct node {
int chl[26];
int finished, fail, fa;
node() {memset(chl, 0, sizeof chl); }
} tree[100005];
int top, root;
queue<int> que;
int str2pos[100005];
graph failTree;
acm():top(1), root(1){}
void init(const char *str)
{
int nd = root;
int cnt = 0;
for (; *str != '\0'; ++str) {
if (*str == 'P') tree[nd].finished = ++cnt, str2pos[cnt] = nd;
else if (*str == 'B') nd = tree[nd].fa;
else {
tree[nd].chl[*str-'a'] = ++top;
tree[top].fa = nd;
nd = top;
}
}
}
void buildFail()
{
tree[root].fail = 0;
for (que.push(root); !que.empty(); que.pop()) {
int k = que.front();
for (int i = 0; i < 26; i++) if (tree[k].chl[i]) {
int t = tree[k].fail;
while (t && !tree[t].chl[i]) t = tree[t].fail;
tree[tree[k].chl[i]].fail = t?tree[t].chl[i]:root;
failTree.push(tree[tree[k].chl[i]].fail, tree[k].chl[i]);
que.push(tree[k].chl[i]);
}
}
failTree.dfs();
}
void dfs(int nd, char c, int tab = 0)
{
if (!nd) return;
for (int i = 1; i <= tab; i++) putchar(' ');
cout << nd << "-->" << c << "(" << tree[nd].finished << "), fail = " << tree[nd].fail << endl;
for (int i = 0; i < 26; i++) dfs(tree[nd].chl[i], i+'a', tab+2);
}
} Acm;
struct bit {
int c[100005];
bit(){memset(c, 0, sizeof c);}
inline int lowbit(int i) {return i&-i;}
void modify(int i, int j)
{ for (; i < 100005; i += lowbit(i)) c[i] += j; }
int sum(int i)
{
int ans = 0;
for (; i; i -= lowbit(i)) ans += c[i];
return ans;
}
inline int sum(int l, int r)
{ return l <= r ? sum(r)-sum(l-1) : 0; }
} Bit;
int x[100005], y[100005], n, ans[100005];
void init()
{
scanf("%s", str);
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d%d", &x[i], &y[i]);
query.push(y[i], x[i], i);
}
Acm.init(str);
Acm.buildFail();
#ifdef Debug
Acm.dfs(1, 'R');
#endif
}
void solve(int nd)
{
#ifdef Debug
cout << "Solving: " << nd << endl;
#endif
Bit.modify(Acm.failTree.L[nd], 1);
if (Acm.tree[nd].finished) {
for (int i = query.head[Acm.tree[nd].finished]; i; i = query.edge[i].next) {
int to = Acm.str2pos[query.edge[i].to], id = query.edge[i].id;
#ifdef Debug
cout << "--From : " << to << " ; id = " << id << endl;
#endif
ans[id] = Bit.sum(Acm.failTree.L[to], Acm.failTree.R[to]);
}
}
for (int i = 0; i < 26; i++)
if (Acm.tree[nd].chl[i])
solve(Acm.tree[nd].chl[i]);
Bit.modify(Acm.failTree.L[nd], -1);
}
int main()
{
freopen("noi2011_type.in", "r", stdin);
freopen("noi2011_type.out", "w", stdout);
init();
solve(1);
for (int i = 1; i <= n; i++)
printf("%d\n", ans[i]);
return 0;
}