题意
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遇到了困难,需要你的帮助。
1 <= n,q <= 100000;
Alice和Bob拥有的字符串长度之和各自都不会超过 2000000;
字符串都由小写英文字母组成。
分析
挺牛逼的一道题。
先对S建AC自动机,得到fail树。然后对于每加入的一个串,在AC自动机上跑。朴素的想法就是,每跑到一个点就把fail树上这个点到根的路径每个点的答案都+1。而对于每次操作每个点只能被加一次。
注意到每次操作的点在fail树上都是若干条从某节点到根的路径的并,有种骚操作就是把每条链的端点按照dfs序排序,然后把每个点到根路径上的点+1,相邻两个点的lca到根路径上的点-1。这里画图感受一下就明白了。
这里路径加可以用差分+树状数组实现。
本以为打起来会很麻烦,但其实也还好,一打完RE了一次之后就A了。
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
const int N=2000005;
int n,m,a[N],dfn_l[N],dfn_r[N],c[N],ch[N][26],sz,cnt,last[N],fa[N],dep[N],rmq[N*2][25],bin[25],tim,dfn_tim,arr[N],lg[N*2],num[100005];
char str[N];
queue<int> que;
struct edge{int to,next;}e[N];
int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
bool cmp(int x,int y) {return dfn_l[x]<dfn_l[y];}
void addedge(int u,int v) {e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt;}
void ins(int x,int y) {while (x<=dfn_tim) c[x]+=y,x+=x&(-x);}
int query(int x) {int ans=0;while (x) ans+=c[x],x-=x&(-x);return ans;}
void get_fail()
{
for (int i=0;i<26;i++) if (ch[0][i]) que.push(ch[0][i]);
while (!que.empty())
{
int u=que.front();que.pop();
for (int i=0;i<26;i++)
if (ch[u][i]) fa[ch[u][i]]=ch[fa[u]][i],que.push(ch[u][i]);
else ch[u][i]=ch[fa[u]][i];
addedge(fa[u],u);
}
}
void dfs(int x)
{
dep[x]=dep[fa[x]]+1;dfn_tim++;dfn_l[x]=dfn_tim;
tim++;rmq[tim][0]=x;arr[x]=tim;
for (int i=last[x];i;i=e[i].next)
{
dfs(e[i].to);
tim++;rmq[tim][0]=x;
}
dfn_r[x]=dfn_tim;
}
void get_rmq()
{
for (int i=1;i<=tim;i++) lg[i]=log(i)/log(2);
for (int j=1;j<=lg[tim];j++)
for (int i=1;i+bin[j]-1<=tim;i++)
rmq[i][j]=dep[rmq[i][j-1]]<dep[rmq[i+bin[j-1]][j-1]]?rmq[i][j-1]:rmq[i+bin[j-1]][j-1];
}
int get_lca(int x,int y)
{
int l=min(arr[x],arr[y]),r=max(arr[x],arr[y]),w=lg[r-l+1];
return dep[rmq[l][w]]<dep[rmq[r-bin[w]+1][w]]?rmq[l][w]:rmq[r-bin[w]+1][w];
}
int main()
{
bin[0]=1;
for (int i=1;i<=20;i++) bin[i]=bin[i-1]*2;
n=read();
for (int i=1;i<=n;i++)
{
scanf("%s",str);
int len=strlen(str),now=0;
for (int j=0;j<len;j++)
if (!ch[now][str[j]-'a']) now=ch[now][str[j]-'a']=++sz;
else now=ch[now][str[j]-'a'];
num[i]=now;
}
get_fail();
dfs(0);
get_rmq();
m=read();
while (m--)
{
int op=read();
if (op==1)
{
scanf("%s",str);
int len=strlen(str),now=0,tot=0;
for (int i=0;i<len;i++) now=ch[now][str[i]-'a'],a[++tot]=now;
sort(a+1,a+tot+1,cmp);
ins(dfn_l[a[1]],1);
for (int i=2;i<=tot;i++)
{
int lca=get_lca(a[i],a[i-1]);
ins(dfn_l[a[i]],1);ins(dfn_l[lca],-1);
}
}
else
{
int x=read();x=num[x];
printf("%d\n",query(dfn_r[x])-query(dfn_l[x]-1));
}
}
return 0;
}

本文介绍使用AC自动机解决字符串查询问题的方法。通过构建AC自动机和差分+树状数组,实现高效查询一个字符串集合中包含特定子串的数量。文章详细解释了解题思路及实现细节。
433

被折叠的 条评论
为什么被折叠?



