bzoj 3881: [Coci2015]Divljak (AC自动机+容斥原理+LCA+树状数组)

题目描述

传送门

题目大意: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遇到了困难,需要你的帮助。

题解

简化一下问题,实际上的问题就是给出了一些短串,和一些长串,问每个短串出现在了多少个长串中。
我们将短串加入trie,然后建立AC自动机,构建出trie树。
假设长串中的某个位置匹配到了AC自动机中的x节点,那么x节点在fail树上到根路径上的所有短串的结尾节点的end都要+1。但是这样子有可能一个短串的end节点别长串的多个位置更新,所以我将所有能匹配到的节点记录下来,按照dfs序排序。设相邻的两个节点为x,y,那么将x到根的路径都+1,y到根的路径+1,lca(x,y)到根的路径-1。应该算是一个简单的容斥原理。
这种区间修改单点查询的问题其实可以转化成单点修改区间查询。
所以我们变成x,y的点权+1,lca(x,y)的点权-1,然后每次查询每个结束点的出现次数就变成了查询这个点子树的值。

代码

#include
#include
#include
#include
#include
#include
#define N 2000003
using namespace std;
int ch[N][30],fail[N],f[N][22],mi[30],l[N],r[N],tr[N],n,m;
int sz,cnt,q[N],point[N],nxt[N],v[N],tot,is_end[N],deep[N];
char s[N];
void insert(int x)
{
    int len=strlen(s+1); int now=0;
    for (int i=1;i<=len;i++) {
        int t=s[i]-'a'+1;
        if (!ch[now][t]) ch[now][t]=++sz;
        now=ch[now][t]; 
    }
    is_end[x]=now;
}
void add(int x,int y)
{
    tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
    //cout<<x<<" "<<y<int> p; int now=0;
    for (int i=1;i<=26;i++)
     if (ch[now][i]) p.push(ch[now][i]);
    while (!p.empty()) {
        now=p.front(); p.pop();
        for (int i=1;i<=26;i++){
         if (!ch[now][i]){
            ch[now][i]=ch[fail[now]][i];
            continue;
         }
         int x=ch[now][i];
         fail[x]=ch[fail[now]][i];
         p.push(x);
        }    
    }
    for (int i=1;i<=sz;i++) add(fail[i],i);
}
int lca(int x,int y)
{
    if (deep[x]y]) swap(x,y);
    int k=deep[x]-deep[y];
    for (int i=0;i<=21;i++)
     if ((k>>i)&1) x=f[x][i];
    if (x==y) return x;
    for (int i=21;i>=0;i--)
     if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}
void dfs(int x)
{
    l[x]=++sz;
    for (int i=1;i<=21;i++) {
        if (deep[x]-mi[i]<0) break;
        f[x][i]=f[f[x][i-1]][i-1];
    }
    for (int i=point[x];i;i=nxt[i]){
        deep[v[i]]=deep[x]+1;
        f[v[i]][0]=x;
        dfs(v[i]);
    }
    r[x]=sz;
}
void solve()
{
    int len=strlen(s+1); int now=0;
    for (int i=1;i<=len;i++) {
        int x=s[i]-'a'+1;
        now=ch[now][x];
        q[++cnt]=now;
    }
}
int lowbit(int x)
{
    return x&(-x);
}
void change(int x,int val)
{
    for (int i=x;i<=sz;i+=lowbit(i))
     tr[i]+=val;
}
int query(int x)
{
    int ans=0;
    if (!x) return 0;
    for (int i=x;i>=1;i-=lowbit(i)) ans+=tr[i];
    return ans;
}
int cmp(int x,int y)
{
    return l[x]y];
}
int main()
{
    freopen("a.in","r",stdin);
    scanf("%d",&n);
    for (int i=1;i<=n;i++) {
        scanf("%s",s+1);
        insert(i);
    }
    make_fail(); mi[0]=1; sz=0;
    for (int i=1;i<=21;i++) mi[i]=mi[i-1]*2;
    deep[0]=1; dfs(0);
    //for (int i=1;i<=n;i++) cout<" ";
    //cout<"%d",&m);
    for (int i=1;i<=m;i++) {
        int opt,x; scanf("%d",&opt);
        if (opt==1) {
            cnt=0; scanf("%s",s+1);
            solve();
            if (!cnt) continue;
            sort(q+1,q+cnt+1,cmp);
            change(l[q[1]],1);
            for (int j=2;j<=cnt;j++) {
                change(l[lca(q[j-1],q[j])],-1);
                change(l[q[j]],1);
            }
        }
        else {
            scanf("%d",&x); x=is_end[x];
            printf("%d\n",query(r[x])-query(l[x]-1));
        }
    }
}

你可能感兴趣的:(LCA,树状数组,容斥原理,AC自动机)