[2018雅礼集训1-16]背单词 AC自动机+二进制分组

题面
大概就是用栈维护 logn 个AC自动机,每次新加入一个单词就新建一个AC自动机,当栈顶的自动机和栈顶下一个自动机包含单词个数相同的时候就暴力重构合并。
一个单词最多被合并 log 次,然后查询的时候也只会跑 log 个自动机,所以复杂度是 O(nlog|t|+|s|logn) 的。其实还带个 26 的常数,不过跑得挺快的。。。
代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
#define N 1000010
using namespace std;
int n,type,cnt,s[N],c[N],a[N][26],b[N][26],fa[N],w[N],bw[N],dl[N],top;
char cs[N];
struct AC
{
    int root,size;
    void init(int *s,int len,int rw)
    {
        root=++cnt;
        for(int i=1;i<=len;i++)
            a[cnt][s[i]]=++cnt;
        w[cnt]=rw;
        size=1; 
    }
    void clr()
    {
        for(int i=cnt;i>=root;i--)
        {
            fa[i]=w[i]=c[i]=0;
            memset(a[i],0,sizeof(a[i]));
        }
        cnt=root-1;
    }
    void getac()
    {
        dl[1]=root;fa[root]=root;
        for(int hd=1,tl=1,v=root;hd<=tl;v=dl[++hd])
            for(int ic=0;ic<=25;ic++)
                if(a[v][ic])
                {
                    int p=fa[v],q=a[v][ic];
                    while(p!=root&&!a[p][ic]) p=fa[p];
                    if(a[p][ic]&&a[p][ic]!=q) fa[q]=a[p][ic];else fa[q]=root;
                    c[q]=c[fa[q]]+w[q];
                    dl[++tl]=q;
                }
    }
    ll run(int *s,int len)
    {
        ll re=0;
        for(int i=1,p=root;i<=len;i++)
        {
            while(p!=root&&!a[p][s[i]]) p=fa[p];
            if(a[p][s[i]]) p=a[p][s[i]];
            re+=c[p]; 
        }
        return re;
    }
}Tac[23];   
void merge(int v,int u)
{
    w[v]+=bw[u];
    for(int ic=0;ic<=25;ic++)
        if(b[u][ic])
        {
            if(!a[v][ic]) a[v][ic]=++cnt; 
            merge(a[v][ic],b[u][ic]);
        }
}
int main()
{
    scanf("%d%d",&n,&type);
    ll ans=0;
    for(int i=1;i<=n;i++)
    {
        int opt,len;
        scanf("%d%s",&opt,cs+1);
        len=strlen(cs+1);
        for(int i=1;i<=len;i++)
            s[i]=((cs[i]-'a')^(type*ans))%26;               
        if(opt) 
        {
            ans=0;
            for(int i=1;i<=top;i++)
                ans+=Tac[i].run(s,len);
            printf("%lld\n",ans);   
        }
        else    
        {
            int rw;
            scanf("%d",&rw);
            Tac[++top].init(s,len,rw);
            while(top>1&&Tac[top].size==Tac[top-1].size)
            {
                for(int i=cnt;i>=Tac[top].root;i--)
                    memcpy(b[i],a[i],sizeof(b[i])),bw[i]=w[i];
                Tac[top-1].size+=Tac[top].size;
                Tac[top--].clr();
                merge(Tac[top].root,cnt+1);
            }
            Tac[top].getac();
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值