码迷,mamicode.com
首页 > 其他好文 > 详细

BZOJ3881 Coci2015 Divljak fail树+差分

时间:2018-01-22 22:54:32      阅读:207      评论:0      收藏:0      [点我收藏+]

标签:query   一个   ret   scan   insert   mda   题目   pre   串匹配   

题目大意,给出两个字符串集合S和T,向T中添加字符串,查询S_i在T中有几个字符串出现过。一看这种多字符串匹配问题,我们联想到了AC

自动机,做法就是,对于S集合我们建立一个AC自动机,建出fail树,fail树有一个很好的性质就是,对于一个节点x,它所对应的字符串是它子树中所有节点对应的字符串的后缀。我们考虑如果S_x在P_x 中出现过,他肯定是P_x某一个前缀的后缀,所以我们把P_x在AC自动机上跑,跑到每一个节点我们更新一下他所在的fail树,统计答案的时候只需统计子树的大小就行了。但是这样会有一点小问题,就是会统计重复,如果对于每一个前缀我们都更新一下它到跟的路径和,这样会重复,因为S_x可能会在P_x中出现多次。实际上我们求的是一个点的子树所有链的并集,解决方案很巧妙,利用树上差分,我们按照dfs序排好顺序,然后相邻的两个节点的lca处-1就行了,这样就不会统计重复,利用树状数组维护一下dfs序即可。(有一点需要注意,在trie树上我们一共有tot个节点,那么对于fail树我们有tot+1个节点,树状数组大小注意一下,一开始错在了这里)——by VANE

#include<bits/stdc++.h>
using namespace std;
const int N=2000010;
char s[N];
vector<int> g[N];
int son[N][30],fail[N],L[N],R[N],a[N];
int sum[N],pos[N],id[N],dep[N];
int mn[N*2][25],LOG[N*2];
int tot,n,dfn,cnt;
int cmp(int x,int y)
{
    return L[x]<L[y];
}
void dfs(int u)
{
    L[u]=++dfn;
    mn[pos[u]=++cnt][0]=u;
    for(int i=0;i<g[u].size();++i)
    {
        int v=g[u][i];
        dep[v]=dep[u]+1;
        dfs(v);
        mn[++cnt][0]=u;
    }
    R[u]=dfn;
}
void insert(int x)
{
    int l=strlen(s+1);
    int p=0;
    for(int j=1;j<=l;++j)
    {
        if(!son[p][s[j]-a]) son[p][s[j]-a]=++tot;
        p=son[p][s[j]-a];
    }
    id[x]=p;
}
void getfail()
{
    queue<int> q;
    for(int i=0;i<26;++i)
    if(son[0][i]) q.push(son[0][i]);
    while(!q.empty())
    {
        int u=q.front();q.pop();
        for(int i=0;i<26;++i)
        if(son[u][i])
        fail[son[u][i]]=son[fail[u]][i],q.push(son[u][i]);
        else son[u][i]=son[fail[u]][i];
    }
}
int query(int x)
{
    int res=0;
    for(;x;x-=x&-x)
    res+=sum[x];
    return res;
}
int lca(int x,int y)
{
    if(pos[x]<pos[y]) swap(x,y);
    int len=pos[x]-pos[y]+1;
    len=LOG[len];
    return min(mn[pos[y]][len],mn[pos[x]-(1<<len)+1][len],cmp);
}
void add(int x,int w)
{
    for(;x<=tot+1;x+=x&-x)
    sum[x]+=w;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%s",s+1);
        insert(i);
    }
    getfail();
    for(int i=1;i<=tot;++i) g[fail[i]].push_back(i);
    dfs(0);
    for(int k=1;(1<<k)<=cnt;++k) LOG[1<<k]=k;
    for(int i=3;i<=cnt;++i)
    if(!LOG[i]) LOG[i]=LOG[i-1];
    for(int k=1;k<=LOG[cnt];++k)
    for(int i=1;i+(1<<k)-1<=cnt;++i)
    mn[i][k]=min(mn[i][k-1],mn[i+(1<<k-1)][k-1],cmp);
    int q;
    scanf("%d",&q);
    while(q--)
    {
        int opt;scanf("%d",&opt);
        if(opt==1)
        {
            scanf("%s",s+1);
            int l=strlen(s+1);
            int p=0;
            for(int i=1;i<=l;++i)
            a[i]=p=son[p][s[i]-a];
            sort(a+1,a+1+l,cmp);
            for(int i=1;i<=l;++i)
            add(L[a[i]],1);
            for(int i=1;i<l;++i)
            {
                int x=lca(a[i],a[i+1]);
                add(L[x],-1);
            }
        }
        else
        {
            int x;
            scanf("%d",&x);
            printf("%d\n",query(R[id[x]])-query(L[id[x]]-1));
        }
    }
}

 

BZOJ3881 Coci2015 Divljak fail树+差分

标签:query   一个   ret   scan   insert   mda   题目   pre   串匹配   

原文地址:https://www.cnblogs.com/nbwzyzngyl/p/8331345.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!