• NOI2011阿狸的打字机(fail树+DFS序)


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

    fail树是这样一个东西:将AC自动机的fail指针连成一棵树(根节点为虚拟节点0)。

    这样有什么好处呢?匹配时沿着fail树向上找就能找到所有匹配位置了:

    void find(char* s)
    {
        int j=0,c;
        for(int i=0;s[i]!='';i++)
        {
            c=s[i]-'a';
            while(j&&!ch[j][c]) j=f[j];
            for(int t=j;t;t=f[t]) if(val[t]) cnt[val[t]]++;
        }
    }
    View Code

     也就是说,对于一个节点c从root(0)走向c连成的字符串(模板串),一个节点v从root(0)走向v连成的字符串(匹配串),匹配串在模板串中出现的次数就是root->c的路径上的节点有多少出现在节点v的子树中。

    这样我们只需维护DFS序即可。

    那么这道题的做法就水落石出了:

    首先构建AC自动机的fail树得出dfs序,得出每个结点进出时间l[x],r[x],考虑这样一种暴力

    对于一个询问x,y,查询自动机上root-y的每一个结点,沿着fail指针是否会走到x的结尾点,如果可以即答案+1

    那么就变为在fail树中,查询自动机上root-y的所有结点中,有多少个在x的子树中。

    只要在自动机上再重新走一遍,走到一个结点y,则将1-l[y]都+1。解决询问x,y(把y相同的链表串起来),即查询l[x]到r[x]的和。当遇到一个B时1-l[y]都-1。

    只需树状数组实现加减和区间求和

    随便写了写,竟然在BZOJ上rank1了,我很开心

    #include<cstdio>
    #include<cctype>
    #include<cstring>
    #include<queue>
    #include<algorithm>
    using namespace std;
    inline int read()
    {
        int x=0,f=1;char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        return x*f;
    }
    void print(int x)
    {
        if(!x) {putchar('0');putchar('
    ');return;}
        int len=0,buf[15];
        while(x) buf[++len]=x%10,x/=10;
        for(int i=len;i;i--) putchar(buf[i]+'0');
        putchar('
    ');
    }
    const int maxn=200010;
    int ch[maxn][26],fa[maxn],f[maxn],pos[maxn],id,sz;
    void insert(char* s)
    {
        int j=0,c;
        for(int i=0;s[i];i++)
        {
            c=s[i]-'a';
            if(s[i]=='P') pos[++id]=j;
            else if(s[i]=='B') j=fa[j];
            else
            {
                if(!ch[j][c]) ch[j][c]=++sz,fa[sz]=j;
                j=ch[j][c];
            }
        }
    }
    queue<int> q;
    void getfail()
    {
        for(int c=0;c<26;c++) if(ch[0][c]) q.push(ch[0][c]);
        while(!q.empty())
        {
            int u=q.front(),v;q.pop();
            for(int c=0;c<26;c++) if(v=ch[u][c])
            {
                q.push(v);int j=f[u];
                while(j&&!ch[j][c]) j=f[j];
                f[v]=ch[j][c];
            }
        }
    }
    int first[maxn],next[maxn],to[maxn],e;
    void AddEdge(int u,int v){to[++e]=v;next[e]=first[u];first[u]=e;}
    int l[maxn],r[maxn],cnt;
    void dfs(int x)
    {
        l[x]=++cnt;
        for(int i=first[x];i;i=next[i]) dfs(to[i]);
        r[x]=++cnt;
    }
    int sumv[maxn];
    void add(int x,int v) {for(;x<=cnt;x+=x&-x) sumv[x]+=v;}
    int sum(int x) {int ret=0;for(;x;x-=x&-x) ret+=sumv[x];return ret;}
    struct Query {int next,v;}Q[maxn];
    int firstq[maxn],ans[maxn];
    void solve(char* s)
    {
        int j=0,c;add(l[j],1);id=0;
        for(int i=0;s[i];i++)
        {
            c=s[i]-'a';
            if(s[i]=='P')
            {
                id++;for(int x=firstq[id];x;x=Q[x].next)
                {
                    int v=pos[Q[x].v];
                    ans[x]=sum(r[v])-sum(l[v]-1);
                }
            }
            else if(s[i]=='B') add(l[j],-1),j=fa[j];
            else j=ch[j][c],add(l[j],1);
        }
    }
    char s[maxn];
    int main()
    {
        scanf("%s",s);insert(s);getfail();
        for(int i=1;i<=sz;i++) AddEdge(f[i],i);
        dfs(0);
        int m=read();
        for(int i=1;i<=m;i++) 
        {
           int x=read(),y=read();
           Q[i].v=x;Q[i].next=firstq[y];firstq[y]=i;
        }
        solve(s);
        for(int i=1;i<=m;i++) print(ans[i]);
        return 0;
    }
    View Code
  • 相关阅读:
    poj 3026 Borg Maze
    poj2828 Buy Tickets
    poj3264 Balanced Lineup
    高精度运算
    poj1035 Spell checker
    poj2318 TOYS 点积叉积理解
    求两直线交点的一般做法
    C语言I博客作业05
    C语言I博客作业04
    C语言I博客作业07
  • 原文地址:https://www.cnblogs.com/wzj-is-a-juruo/p/4558198.html
Copyright © 2020-2023  润新知