bzoj 2434: [Noi2011]阿狸的打字机 (AC自动机,dfs序,BIT)

[Submit][Status][Discuss]

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;
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值