Description
阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有28个按键,分别印有26个小写英文字母和'B'、'P'两个字母。
经阿狸研究发现,这个打字机是这样工作的:
l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。
l 按一下印有'B'的按键,打字机凹槽中最后一个字母会消失。
l 按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。
例如,阿狸输入aPaPBbP,纸上被打印的字符如下:
a
aa
ab
我们把纸上打印出来的字符串从1开始顺序编号,一直到n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数(x,y)(其中1≤x,y≤n),打字机会显示第x个打印的字符串在第y个打印的字符串中出现了多少次。
阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?
Input
输入的第一行包含一个字符串,按阿狸的输入顺序给出所有阿狸输入的字符。
第二行包含一个整数m,表示询问个数。
接下来m行描述所有由小键盘输入的询问。其中第i行包含两个整数x, y,表示第i个询问为(x, y)。
Output
输出m行,其中第i行包含一个整数,表示第i个询问的答案。
Sample Input
aPaPBbP
3
1 2
1 3
2 3
Sample Output
2
1
0
HINT
1<=N<=10^5
1<=M<=10^5
输入总长<=10^5
思路:
这个题让我们求一个串在另外一个串中出现的次数。如果暴力用AC自动机的话,一定会超时的。
这个时候就需要我们好好理解一下AC自动机的原理,还有 fail 指针的用处。
如果 B 串中的一个字母的失配指针指向 A 串的末尾,那么 A 串一定存在于 B 串中,
B 串中有多少个失配指针指向A串,那么B串中就有多少个A串。
很好想到一个算法,我们枚举 B串,看看B串中的字母是不是指向 A串的末尾,如果是 那就 +1.
但是这个算法的复杂度太高。
这个时候,我们可以倒过来想一想。失配指针倒过来建边。
从 B 串中指向A的末尾,也就是 从 A的末尾指向B串中的字母,这时候,我们可以找A串的末尾有多少边指向B串,也就是A串
的子树有多少个,
我们又会想到,A串中的子树不一定都指向 B串啊。有可能还会指向其他串啊。
这就需要我们把需要询问的 y 串排个序了。根据 y 从小到大排序,保证询问 x 串的时候, x 的子树都是我们需要的,
求一个节点的子树有多少个,这个就可以用 dfs 序 + BIT 来做了。
先用 fail指针反向建边,跑一边 dfs序,每个节点都有两个时间戳。
字符串中遇到 P,就是该询问的时候了,看这个P是不是 第 y 个P,也就是第 y 个串。
询问就是在 BIT中询问,,查询区间 in[x] ~ out[x] 之间的值。
遇到 B,说明我们要删除一个字母,我们就 in[x] 的位置 -1 就好了。
其他情况 ,在 in[x] 位置 +1,。
/**************************************************************
Problem: 2434
User: yuxiao
Language: C++
Result: Accepted
Time:440 ms
Memory:17164 kb
****************************************************************/
#include <bits/stdc++.h>
#define mem(x,v) memset(x,v,sizeof(x))
#define go(i,a,b) for (int i = a; i <= b; i++)
#define og(i,a,b) for (int i = a; i >= b; i--)
#define low(x) (x & (-x))
using namespace std;
typedef long long LL;
const double EPS = 1e-10;
const int INF = 0x3f3f3f3f;
const int N = 1e5+1000;
struct Tree{
int vis[26],fail;
}Ac[N];
struct point{
int x,y,id;
bool operator < (const point &a) const {
return y < a.y;
}
}g[N];
int pos[N],times,ans[N],m;
int cnt,cur,sz,in[N],out[N],fa[N],c[N];
char s[N];
int p[N],Next[N],Head[N];
int read() {
char ch = getchar(); int x = 0, f = 1;
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
void Add(int x, int y){
while(x <= times) c[x] += y,x += low(x);
}
int Query(int x){
int ans = 0;
while(x > 0) ans += c[x],x-=low(x);
return ans;
}
void Add_edge(int u, int v){
Next[++cur] = Head[u];
Head[u] = cur;
p[cur] = v;
}
void dfs(int x){
in[x] = ++ times;
for(int i = Head[x]; i != -1; i = Next[i]) dfs(p[i]);
out[x] = times;
}
void Build(){
int now = 0,len = strlen(s);
go(i,0,len-1){
if (s[i] == 'P')pos[++cnt] = now;
else if (s[i] == 'B') now = fa[now];
else{
int id = s[i] - 'a';
if (!Ac[now].vis[id]) fa[Ac[now].vis[id] = ++sz] = now;
now = Ac[now].vis[id];
}
}
}
void Get_Fail(){
queue<int>Q; while(!Q.empty()) Q.pop();
go(i,0,25) if (Ac[0].vis[i]!=0) {
Ac[Ac[0].vis[i]].fail = 0;
Q.push(Ac[0].vis[i]);
}
while(!Q.empty()){
int u = Q.front(); Q.pop();
go(i,0,25){
if (Ac[u].vis[i] != 0) {
Ac[Ac[u].vis[i]].fail = Ac[Ac[u].fail].vis[i];
Q.push(Ac[u].vis[i]);
} else
Ac[u].vis[i] = Ac[Ac[u].fail].vis[i];
}
}
}
void Slove(){
int now = 0,k = 1; cnt = 0;
int len = strlen(s);
go(i,0,len-1){
if (s[i] == 'P'){
cnt++;
while(g[k].y == cnt && k <= m){ //看看当前串是不是第 y 个串。
int t = pos[g[k].x];
ans[g[k].id] = Query(out[t]) - Query(in[t]-1);
k++;
}
} else if (s[i] == 'B') Add(in[now],-1),now = fa[now];
else Add(in[now = Ac[now].vis[s[i]-'a']],1);
}
}
int main(){
scanf("%s",s);
Build();
Get_Fail();
mem(Head,-1); cur = -1;
go(i,1,sz) Add_edge(Ac[i].fail,i);
dfs(0);
m = read();
go(i,1,m){ //这里用了快速读入。
g[i].x = read();
g[i].y = read();
g[i].id = i;
}
sort(g+1,g+m+1);
Slove();
go(i,1,m) printf("%d\n",ans[i]);
return 0;
}

本文介绍了一种利用AC自动机解决特定字符串匹配问题的方法。通过倒置失配指针并结合深度优先搜索序与树状数组,高效地求解一个字符串作为子串在另一个字符串中出现的次数。
&spm=1001.2101.3001.5002&articleId=81950391&d=1&t=3&u=797f4a231a2e4ab8b26cccb863b09799)
163

被折叠的 条评论
为什么被折叠?



