题目描述
有一个缓存槽,设计一个程序维护下面三个操作:
∙insert c
:在缓存槽末尾插入小写字符
c
∙delete
:删除缓存槽最后一个字符
操作总共有
n
个。
在所有操作结束之后,要支持
1≤n,m≤100000
题目分析
这道题需要灵活运用
AC
自动机的性质。我搞了一个下午,发现自己原先对
AC
自动机的理解不够透彻。做掉这题之后收获比较多。
最暴力的方法肯定就是将所有询问全部搞出来跑
KMP
,但是这种方法太蠢了。显然,我们将把有串建一个
AC
自动机会更好。由于操作的特殊性,我们能可以通过模拟指针在
Trie
上的进退来在
O(n)
的时间复杂度完成建
Trie
操作(每次操作最多会改变一位字符)。
那么我们建好
AC
自动机之后怎么去求答案呢?首先我们要清楚
AC
自动机的一条性质:一条从
root
到
x
的路径上所有节点以及它们的
那么如果
但是这样还是太蠢了,时间复杂的特别大。所以我们逆向思维,一个以点
x
为结尾的字符串的,从点
所以我们建出
fail
树(所有
fail
边反向构成),那么询问就变为查询
x
为根的子树中在
怎么解决这个询问呢?我们对
fail
树进行
DFS
,求出
DFS
序。那么一棵子树中所有点的序都是连续的,我们将点
x
挂在点
时间复杂度
O((n+m)logn2)
,空间复杂度
O(n)
。
具体细节见代码实现。
收获小结
∙
建立
AC
自动机的
fail
树,然后数据结构维护是很多字符串习题的常用方法,要注意学习。
∙
AC
自动机的性质要理解透。
代码实现
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
const int N=100005;
const int M=100005;
const int S=100005;
const int C=26;
int top[N],nxt[M],qu[M],w[N],ans[M];
int n,m,l,mt;
char str[S];
struct BIT
{
int num[S];
int lowbit(int x)
{
return x&-x;
}
void edit(int x,int y)
{
while (x<S)
{
num[x]+=y;
x+=lowbit(x);
}
}
int query(int x)
{
int ret=0;
while (x)
{
ret+=num[x];
x-=lowbit(x);
}
return ret;
}
}bit;
struct TREE
{
int last[S],next[S],tov[S],DFN[S],size[S];
int tot,ind;
void insert(int x,int y)
{
tov[++tot]=y;
next[tot]=last[x];
last[x]=tot;
}
void dfs(int x)
{
DFN[x]=++ind;
size[x]=1;
int i=last[x],y;
while (i)
{
y=tov[i];
dfs(y);
size[x]+=size[y];
i=next[i];
}
}
}t;
struct AC_automation
{
int next[S][C],fail[S],fa[S];
int tot,root;
queue<int> q;
int newnode()
{
for (int i=0;i<C;i++)
next[tot][i]=-1;
fail[tot]=-1;
return tot++;
}
void init()
{
tot=0;
root=newnode();
}
void insert()
{
int rt=root;
for (int i=0;i<l;i++)
if (str[i]=='B')
rt=fa[rt];
else
if (str[i]=='P')
w[++n]=rt;
else
{
if (next[rt][str[i]-'a']==-1)
{
next[rt][str[i]-'a']=newnode();
fa[next[rt][str[i]-'a']]=rt;
}
rt=next[rt][str[i]-'a'];
}
}
void build()
{
for (int i=0;i<C;i++)
if (next[root][i]==-1)
next[root][i]=root;
else
{
fail[next[root][i]]=root;
q.push(next[root][i]);
}
while (!q.empty())
{
int x=q.front();
for (int i=0;i<C;i++)
if (next[x][i]==-1)
next[x][i]=next[fail[x]][i];
else
{
fail[next[x][i]]=next[fail[x]][i];
q.push(next[x][i]);
}
q.pop();
}
for (int i=1;i<tot;i++)
t.insert(fail[i],i);
}
void solve()
{
t.dfs(root);
n=0;
int rt=root;
bit.edit(t.DFN[0],1);
for (int i=0;i<l;i++)
if (str[i]=='B')
{
bit.edit(t.DFN[rt],-1);
rt=fa[rt];
}
else
if (str[i]=='P')
{
int j=top[++n],x;
while (j)
{
x=qu[j];
x=w[x];
ans[j]=bit.query(t.DFN[x]+t.size[x]-1)-bit.query(t.DFN[x]-1);
j=nxt[j];
}
}
else
{
rt=next[rt][str[i]-'a'];
bit.edit(t.DFN[rt],1);
}
bit.edit(t.DFN[0],-1);
}
}ac;
void hang(int x,int y)
{
qu[++mt]=y;
nxt[mt]=top[x];
top[x]=mt;
}
int main()
{
freopen("type.in","r",stdin);
freopen("type.out","w",stdout);
scanf("%s",str);
l=strlen(str);
ac.init();
ac.insert();
ac.build();
scanf("%d",&m);
for (int i=1,x,y;i<=m;i++)
{
scanf("%d%d",&x,&y);
hang(y,x);
}
ac.solve();
for (int i=1;i<=m;i++)
printf("%d\n",ans[i]);
fclose(stdin);
fclose(stdout);
return 0;
}