题意:
分析:
字符串题要求强制在线,进行删除,插入,匹配
这里提供三种做法:
-
根号分治
对于这类字符串匹配问题,我们能直接想到的暴力做法就是建 Trie,或者直接 KMP, 但是 Trie 空间复杂度不太对劲,KMP 时间复杂度不太对劲,那就把两种做法合并一下,对于长度大于 \(\sqrt s\) 的直接暴力 KMP,否则插入 Trie 树中匹配
-
根号重构
除了常见的暴力以外,像这种多文本串的字符串匹配的问题还可以用 AC 自动机,但是尴尬的是 AC 自动机不支持删除,而且每次插入会改变 AC 自动机的形态,需要重新调整 \(fail\) 指针,所以单次插入最劣变成 \(O(n)\) 的,那么怎么办呢?
对于第一个问题很好解决 ,我们每次删除的时候也建一个关于被删除串的 AC 自动机,这样每一次将两个询问作差得到的就是答案
对于第二个问题,我们也可以利用上面的根号分治的思想,既然单次插入的复杂度过高,那我们就每 \(\sqrt m\) 个串建一个 AC 自动机每插入 \(\sqrt m\) 个字符串之后就重构两个 AC 自动机,将他们合并
-
二进制分组
其实这个做法和上面的重构差不多,只不过是,把重构的阈值改成了 \(2\) 的幂次,这样大小很小的时候重构次数较多,大小较大的时候重构次数就会变少,比较优秀,因为每个串被重构的次数不会超过 \(O(\log m)\) 次
tip: Trie 树重构的时候,不能直接在 原来的 Trie 的 ch 数组上建出新的树形,因为可能存在多次重构,所以必须要保留原来的树形,要另外开一个数组表示每一次重构后的 Trie 树
代码:
这里提供的是二进制分组的写法,之前听学姐讲过一遍,今天才算是明白了
#include<bits/stdc++.h>
#define pii pair<int,int>
#define mk(x,y) make_pair(x,y)
#define lc rt<<1
#define rc rt<<1|1
#define pb push_back
#define fir first
#define sec second
#define inl inline
#define reg register
using namespace std;
namespace zzc
{
typedef long long ll;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
const int maxn = 3e5+5;
char s[maxn];
int m,tot,idx;
int rt[maxn],siz[maxn],num[maxn],fail[maxn],ch[maxn][26],ch2[maxn][26],pw[maxn];
struct AC
{
int st[maxn],top;
queue<int> q;
int merge(int x,int y)
{
if(!x||!y) return x|y;
for(int i=0;i<26;i++) ch[x][i]=merge(ch[x][i],ch[y][i]);
num[x]+=num[y];
return x;
}
void build(int id)
{
for(int i=0;i<26;i++)
{
if(ch[rt[id]][i]) fail[ch2[rt[id]][i]=ch[rt[id]][i]]=rt[id],q.push(ch2[rt[id]][i]);
else ch2[rt[id]][i]=rt[id];
}
while(!q.empty())
{
int x=q.front();q.pop();
siz[x]=num[x]+siz[fail[x]];
for(int i=0;i<26;i++)
{
if(ch[x][i])
{
fail[ch2[x][i]=ch[x][i]]=ch2[fail[x]][i];
q.push(ch2[x][i]);
}
else ch2[x][i]=ch2[fail[x]][i];
}
}
}
void insert(char *s)
{
int len=strlen(s+1);
int id=++idx;
if(!rt[id])rt[id]=++tot;
int now=rt[id];
for(int i=1;i<=len;i++)
{
int c=s[i]-'a';
ch[now][c]=++tot;
now=ch[now][c];
}
++num[now];pw[id]++;
while(top&&pw[st[top]]==pw[id]) rt[id]=merge(rt[id],rt[st[top]]),pw[id]+=pw[st[top--]];
build(id);
st[++top]=id;
}
int query(char *s)
{
int len=strlen(s+1),res=0;
for(int i=1;i<=top;i++)
{
int now=rt[st[i]];
for(int j=1;j<=len;j++)
{
int c=s[j]-'a';
now=ch2[now][c];
res+=siz[now];
}
}
return res;
}
}f,d;
void work()
{
int opt;
m=read();
while(m--)
{
opt=read();scanf("%s",s+1);
if(opt==1) f.insert(s);
else if(opt==2) d.insert(s);
else printf("%d\n",f.query(s)-d.query(s));
fflush(stdout);
}
}
}
int main()
{
zzc::work();
return 0;
}