大致题意: 已知(n)个字符串和一个空的字符串集。两种操作:往字符串集中加入一个字符串;询问(n)个字符串中的某个字符串是字符串集中多少个字符串的子串。
前言
一看完题面,我觉得这是道水题,但等真正开始写的时候,我发现,这真是一道水题。。。
然而,这道题不光数据范围有问题,还卡内存,这就非常令人绝望了。
(500MB)的内存限制我最后好不容易卡在了(494.88MB)。。。
卡内存三部曲:
- 把倍增(LCA)改成树剖(LCA)。
- 发现把单向边智障地开了两倍数组,赶紧改了。
- 异想天开地把树剖中重儿子数组重复利用作链首数组。
一道类似的题目+子串的转化
有一道和这题非常像的题目:【BZOJ2434】[NOI2011] 阿狸的打字机。
我们需要知道,子串就等同于前缀的后缀,而在(AC)自动机上有这样两个性质:
- (A)串是(B)串的前缀,说明(A)串在(AC)自动机上对应的节点在根节点到(B)串的路径上。
- (A)串是(B)串的后缀,说明(B)串在(AC)自动机上对应的节点跳了若干次(fail)指针后能够到达(A)串。
于是,如果我们把(AC)自动机的(fail)树通过(dfs)序转化为数列,然后插入一个字符串就相当于把(AC)自动机上根到其对应节点的链上的所有点打个标记,询问就相当于询问(fail)树中其对应节点子树内的标记种数。
一些细节
考虑如何处理标记。
可以使用差分,将所有要打标记的点按(dfs)序排序后,给每个点的权值加(1),然后给相邻两个点(LCA)的权值减(1)。容易发现这样每个字符串的贡献只会被计算一次了。
考虑如何处理询问。
这太显然了。子树在数列上相当于一个区间,只要用树状数组,就可以方便地实现单点修改、区间查询。
然后似乎就没啥了吧,具体实现可见代码。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define swap(x,y) (x^=y^=x^=y)
using namespace std;
int n,m,p[N+5];string s[N+5];struct data {int op,id;}q[N+5];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define pc(c) (C==E&&(clear(),0),*C++=c)
#define tn (x<<3)+(x<<1)
#define D isdigit(c=tc())
int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
public:
I FastIO() {A=B=FI,C=FO,E=FO+FS;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
I void reads(string& x) {x="";W(isspace(c=tc()));W(x+=c,!isspace(c=tc())&&~c);}
Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
Tp I void writeln(Con Ty& x) {write(x),pc('
');}
I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
class AcAutomation//AC自动机
{
private:
#define SZ 4000000
int Nt,S[SZ+5][26],F[SZ+5],q[SZ+5];
class FailTree//Fail树
{
private:
#define LG 21
int d,dfn[SZ+5],dep[SZ+5],fa[SZ+5],tp[SZ+5],sz[SZ+5];
int n,ee,lnk[SZ+5];struct edge {int to,nxt;}e[SZ];
struct data
{
int x,y;I data(CI a=0,CI b=0):x(a),y(b){}
I bool operator < (Con data& o) Con {return y<o.y;}
}s[SZ+5];
class TreeArray//树状数组
{
private:
int n,a[SZ+5];
public:
I void Init(CI x) {n=x;}I void U(RI x,CI v) {W(x<=n) a[x]+=v,x+=x&-x;}//单点修改
I int Q(RI x,RI t=0) {W(x) t+=a[x],x-=x&-x;return t;}//区间求值
}T;
I void dfs1(CI x)//树剖第一遍dfs
{
sz[x]=1;for(RI i=lnk[x];i;i=e[i].nxt) dep[e[i].to]=dep[fa[e[i].to]=x]+1,
dfs1(e[i].to),sz[x]+=sz[e[i].to],sz[e[i].to]>sz[tp[x]]&&(tp[x]=e[i].to);
}
I void dfs2(CI x,CI p)//树剖第二遍dfs
{
dfn[x]=++d,tp[x]&&(dfs2(tp[x],p),0);
for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^tp[x]&&(dfs2(e[i].to,e[i].to),0);tp[x]=p;
}
I int LCA(RI x,RI y)//树剖LCA
{
W(tp[x]^tp[y]) dep[tp[x]]>dep[tp[y]]?x=fa[tp[x]]:y=fa[tp[y]];
return dep[x]<dep[y]?x:y;
}
public:
I void Init(CI x) {T.Init(n=x),dfs1(1),dfs2(1,1);}//预处理
I void Add(CI x,CI y) {e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y;}
I void U(int *q,CI n)//修改
{
RI i;for(i=1;i<=n;++i) s[i]=data(q[i],dfn[q[i]]);sort(s+1,s+n+1);//按dfs序排序
for(T.U(s[1].y,1),i=1;i^n;++i) T.U(s[i+1].y,1),T.U(dfn[LCA(s[i].x,s[i+1].x)],-1);//差分
}
I int Q(CI x) {return T.Q(dfn[x]+sz[x]-1)-T.Q(dfn[x]-1);}//求子树权值和
}P;
public:
I AcAutomation() {Nt=1;}
I int Ins(Con string& s)//插入字符串
{
RI i,l=s.length(),x=1,nxt;for(i=0;i^l;++i)
!S[x][nxt=s[i]-97]&&(S[x][nxt]=++Nt),x=S[x][nxt];
return x;
}
I void Build()//建AC自动机
{
RI i,k,H=1,T=0;for(i=0;i^26;++i) (S[1][i]?F[q[++T]=S[1][i]]:S[1][i])=1;
W(H<=T) for(k=q[H++],i=0;i^26;++i) (S[k][i]?F[q[++T]=S[k][i]]:S[k][i])=S[F[k]][i];
for(i=2;i<=Nt;++i) P.Add(F[i],i);P.Init(Nt);//建Fail树
}
I void U(Con string& s) {RI i,l=s.length(),x=1,t=0;for(i=0;i^l;++i) q[++t]=x=S[x][s[i]-97];P.U(q,t);}//修改
I int Q(CI x) {return P.Q(x);}//询问
}AC;
int main()
{
RI i;for(F.read(n),i=1;i<=n;++i) F.reads(s[0]),p[i]=AC.Ins(s[0]);//p记录对应点
RI t=0;for(F.read(m),i=1;i<=m;++i) F.read(q[i].op),//离线存下询问,以建AC自动机
q[i].op==1?(F.reads(s[q[i].id=++t]),AC.Ins(s[t])):(F.read(q[i].id),0);
for(AC.Build(),i=1;i<=m;++i)
q[i].op==1?AC.U(s[q[i].id]):F.writeln(AC.Q(p[q[i].id]));//处理操作
return F.clear(),0;
}