CF710F String Set Queries

一、题目

点此看题

二、解法

本题要求强制在线,但是蒟蒻作者只会离线,也讲一讲吧,虽然和本题关系不大,不想听的巨佬可以跳过这一部分。

可以先把所有 1 1 1操作的字符串插入 AC \text{AC} AC自动机,然后回到第一步,插入和删除都可以看作对 f a i l fail fail字符的区间加减,询问直接在当前时刻的自动机上跳就行了。


那离线怎么做呢?首先删除可以理解成插入自动机和删除自动机的差分,那删除也就变成了插入。我们把字符串二进制分组,也就是维护 log ⁡ n \log n logn AC \text{AC} AC自动机,每次插入把最低位的 0 0 0后面的 1 1 1并到上面来,然后重建这个自动机的 f a i l fail fail指针,询问时在每一个自动机上跑,把答案累加就行了。

这种算法的复杂度是多少呢?由于每个字符串只会被重构 log ⁡ n \log n logn次,所以总时间复杂度是 O ( n log ⁡ n ) O(n\log n) O(nlogn),口胡可能难以理解,请参考我的代码。

#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int M = 300005;
int read()
{
    int x=0,flag=1;
    char c;
    while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
    while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*flag;
}
int n,op;char s[M];
struct automaton
{
    int cnt,rt[20],c[M][26],to[M][26],val[M],fail[M];
    automaton() {cnt=20;}
    void merge(int x,int y)
    {
        val[y]|=val[x];
        for(int i=0;i<26;i++)
        {
            if(!c[x][i]) continue;
            if(!c[y][i]) c[y][i]=c[x][i];
            else merge(c[x][i],c[y][i]);
        }
    }
    void build(int rt)
    {
        queue<int> q;
        fail[rt]=0;
        for(int i=0;i<26;i++)
            if(c[rt][i]) fail[to[rt][i]=c[rt][i]]=rt,q.push(c[rt][i]);
            else to[rt][i]=rt;
        while(!q.empty())
        {
            int t=q.front();
            q.pop();
            val[t]+=val[fail[t]];
            for(int i=0; i<26; i++)
                if(c[t][i]) fail[to[t][i]=c[t][i]]=to[fail[t]][i],q.push(c[t][i]);
                else to[t][i]=to[fail[t]][i];
        }
    }
    void ins(char *s)
    {
        int len=strlen(s),p=20;
        for(int i=0;i<len;i++)
        {
            int v=s[i]-'a';
            if(!c[p][v]) c[p][v]=++cnt;
            p=c[p][v];
        }val[p]=1;
        for(int i=0;i<19;i++)
        {
            if(!rt[i])
            {
                rt[i]=i+1;
                memcpy(c[rt[i]],c[20],sizeof c[20]);
                memset(c[20],0,sizeof c[20]);
                build(rt[i]);
                break;
            }
            else
            {
                merge(rt[i],20);
                rt[i]=0;
            }
        }
    }
    int query(char *s)
    {
        int len=strlen(s),ans=0;
        for(int i=0;i<19;i++)
        {
            if(rt[i]==0) continue;
            int p=rt[i];
            for(int j=0;j<len;j++)
            {
                p=to[p][s[j]-'a'];
                ans+=val[p];
            }
        }
        return ans;
    }
}A,D;
int main()
{
    n=read();
    while(n--)
    {
        op=read();scanf("%s",s);
        if(op==1) A.ins(s);
        if(op==2) D.ins(s);
        if(op==3) printf("%d\n",A.query(s)-D.query(s));
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值