zoukankan      html  css  js  c++  java
  • BZOJ3881[Coci2015]Divljak——AC自动机+树状数组+LCA+dfs序+树链的并

    题目描述

    Alice有n个字符串S_1,S_2...S_n,Bob有一个字符串集合T,一开始集合是空的。
    接下来会发生q个操作,操作有两种形式:
    “1 P”,Bob往自己的集合里添加了一个字符串P。
    “2 x”,Alice询问Bob,集合T中有多少个字符串包含串S_x。(我们称串A包含串B,当且仅当B是A的子串)
    Bob遇到了困难,需要你的帮助。

    输入

    第1行,一个数n;
    接下来n行,每行一个字符串表示S_i;
    下一行,一个数q;
    接下来q行,每行一个操作,格式见题目描述。

    输出

    对于每一个Alice的询问,帮Bob输出答案。

    样例输入

    3
    a
    bc
    abc
    5
    1 abca
    2 1
    1 bca
    2 2
    2 3

    样例输出

    1
    2
    1

    提示

    【数据范围】

    1 <= n,q <= 100000;

    Alice和Bob拥有的字符串长度之和各自都不会超过 2000000;

    字符串都由小写英文字母组成。

    这道题和bzoj2434都是很好的AC自动机练习题,都利用了fail树的性质来解决字符串问题。首先要知道一点:如果x串是y串的子串,那x串一定是y串一个前缀的后缀。我们知道AC自动机上每个点表示这个点到根节点的字符串。这道题的大体思路是把S集合建成AC自动机,每添加一个T集合中的字符串P就把他在AC自动机上跑一遍,把所有遍历的点及它们fail指针能达到的所有点的答案数都加1(遍历的点自然是这个串的子串,fail指针能达到的点都是遍历的点的后缀,当然也是加入的串的子串),然后查询。利用fail树的性质可以发现,每次插入一个串P,除了遍历的点,其他答案要加1的点都是这些点在fail树上的祖先,因此只要用树上差分将遍历的点+1就把所有答案要加的点都加了。但这样有的点就会被多加好几次,每次插入却最多只能把每个点加一次,因此,要把遍历的点按dfs序排序,然后将相邻两个点的lca到根节点答案都-1(也就是用差分将lca-1)就去重了(因为按dfs序相邻两个点最近,lca也最深)。查询时用树状数组在dfs序上区间求和就行了。

    最后附上代码。

    #include<cmath>
    #include<queue>
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    int n,m;
    int sum;
    int num;
    int cnt;
    int tot;
    int opt;
    int g[100010];
    int k[2000010];
    int v[2000010];
    int l[2000010];
    int r[2000010];
    int d[2000010];
    char s[2000010];
    int to[2000010];
    int head[2000010];
    int next[2000010];
    int fail[2000010];
    int f[23][2000010];
    int a[2000010][26];
    void add(int x,int y)
    {   
        tot++;
        next[tot]=head[x];
        head[x]=tot;
        to[tot]=y;
    }
    void change(int x,int val)
    {
        for(int i=x;i<=num;i+=i&-i)
        {
            v[i]+=val;
        }
    }
    int ask(int x)
    {
        int res=0;
        for(int i=x;i;i-=i&-i)
        {
            res+=v[i];
        }
        return res;
    }
    void build(char *s,int c)
    {
        int now=0;
        int len=strlen(s);
        for(int i=0;i<len;i++)
        {
            int x=s[i]-'a';
            if(!a[now][x])
            {
                a[now][x]=++cnt;
            }
            now=a[now][x];
        }
        g[c]=now;
    }
    void getfail()
    {
        queue<int>q;
        for(int i=0;i<26;i++)
        {
            if(a[0][i])
            {
                q.push(a[0][i]);
                fail[a[0][i]]=0;
            }
        }
        while(!q.empty())
        {
            int now=q.front();
            q.pop();
            add(fail[now],now);
            f[0][now]=fail[now];
            for(int i=0;i<26;i++)
            {
                if(a[now][i])
                {
                    fail[a[now][i]]=a[fail[now]][i];
                    q.push(a[now][i]);
                }
                else
                {
                    a[now][i]=a[fail[now]][i];
                }
            }
        }
    }
    bool cmp(int x,int y)
    {
        return l[x]<l[y];
    }
    void dfs(int x)
    {
        l[x]=++num;
        d[x]=d[f[0][x]]+1;
        for(int i=1;i<=21;i++)
        {
            f[i][x]=f[i-1][f[i-1][x]];
        }
        for(int i=head[x];i;i=next[i])
        {
            dfs(to[i]);
        }
        r[x]=num;
    }
    int lca(int x,int y)
    {
        if(d[x]<d[y])
        {
            swap(x,y);
        }
        int dep=d[x]-d[y];
        for(int i=0;i<=21;i++)
        {
            if(((1<<i)&dep)!=0)
            {
                x=f[i][x];
            }
        }
        if(x==y)
        {
            return x;
        }
        for(int i=21;i>=0;i--)
        {
            if(f[i][x]!=f[i][y])
            {
                x=f[i][x];
                y=f[i][y];
            }
        }
        return f[0][x];
    }
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%s",s);
            build(s,i);
        }
        getfail();
        dfs(0);
        scanf("%d",&m);
        for(int i=1;i<=m;i++)
        {
            scanf("%d",&opt);
            if(opt==1)
            {
                scanf("%s",s);
                int len=strlen(s);
                int now=0;
                int x=0;
                for(int j=0;j<len;j++)
                {
                    now=a[now][s[j]-'a'];
                    k[++x]=now;
                }
                sort(k+1,k+1+x,cmp);
                for(int j=1;j<=x;j++)
                {
                    change(l[k[j]],1);
                }
     
                for(int j=1;j<x;j++)
                {
                    change(l[lca(k[j],k[j+1])],-1);
                }
            }
            else
            {
                scanf("%d",&sum);
                printf("%d
    ",ask(r[g[sum]])-ask(l[g[sum]]-1));
            }
        }
    }
  • 相关阅读:
    List数据去重的五种有效方法
    select 1 from ... sql语句中的1代表什么意思?
    gitlab 创建一个空的分支 将本地代码推到特定分支
    Error running 'dt-assets-monitor [clean]': Cannot run program "C:Program Files (x86)Javajdk1.8.0_73injava.exe" (in directory "E:codedt-assets-monitor")
    Git SSH Key 生成步骤
    Git,GitHub与GitLab的区别
    聊聊TCP Keepalive、Netty和Docker
    centos7设置非图形界面
    PHP mysqli 使用预处理语句防注入
    用传纸条讲 HTTPS
  • 原文地址:https://www.cnblogs.com/Khada-Jhin/p/9171881.html
Copyright © 2011-2022 走看看