大致题意: 每次在字符串后面加入或删除一个字符,求本质不同的子串个数。
后缀自动机
先说明,此题后缀自动机的确能过。
但我的后缀自动机比较弱,遇上一个较强的(Hack)数据就被卡掉了。。。(可见这场比赛的(T1):【HHHOJ】ZJOI2019模拟赛(十三)03.10)
尽管如此,还是讲一下大致思路吧。
更新答案
考虑后缀自动机求解本质不同的子串个数时,我们需要统计(Len_x-Len_{fa_x})。
所以我们可以简单定义一个(F5)函数来刷新答案:
#define F5(x,op) (void)(ans+=1LL*(op)*(O[x].L-O[GetFa(x)].L))
插入操作
考虑后缀自动机的插入过程,对于一个字符,我们可能会新建(1sim2)个节点,且其中第一个节点需要建立于上一个字符第一个节点的基础之上,而第二个节点是作为辅助节点。
注意在插入的同时要更新(ans)。
由于每次插入需要上一个插入的节点信息,因此我们需要开个栈,来存储还在字符串中的字符编号。
删除操作
考虑删除,每次最多只需删除两个节点。
我们可以标记被删除的节点(Ex=0),存在的节点(Ex=1),然后记录每个节点的后继(Nxt),求的时候跳(Nxt)并路径压缩一下即可。
删除一个节点后,其所有子节点都变成了它父亲的儿子,因此要将(Sz_{fa_x})先减去(1),再加上(Sz_x),即加上(Sz_x-1)。
更新贡献时,设这个节点为(x),若要减去它的贡献,就是(F5(x,-1))。
但删去它之后,它所有子节点与其父节点(L)的差值都增加了(Len_x-Len_{fa_x}),也就相当于(F5(x,Sz_x))。
合起来便是(F5(x,Sz_x-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 LL long long
using namespace std;
char s[N+5];
class SuffixAutomation//后缀自动机
{
private:
#define F5(x,op) (void)(ans+=1LL*(op)*(O[x].L-O[GetFa(x)].L))//更新答案
#define Co(x,y) (void)(++O[O[x].F=y].Sz)//连边
static const int SZ=N,C=26;int tot;LL ans;
struct SAM {int L,F,Sz,Ex,Nxt,S[C+5];}O[(SZ<<1)+5];
I int GetFa(CI x) {return O[O[x].F].Ex?O[x].F:O[x].F=GetFa(O[x].F);}//求第一个存在的父亲
I int GetNxt(int& x) {return O[x].Ex?x:x=GetNxt(O[x].Nxt);}//求出第一个存在的后继
public:
int ExSt[N+5],SamP[N+5],TwoP[N+5];//ExSt为一个记录存在节点的栈,SamP和TwoP分别存储一个字符在SAM中建的两个节点的编号
I SuffixAutomation() {O[0].Ex=O[SamP[0]=tot=1].Ex=1;}//初始化后缀自动机
I void Insert(CI x,CI id,CI lst)//插入字符
{
RI p=lst,q,k,now=SamP[id]=++tot;O[now].L=O[p].L+1,O[now].Ex=1;
W(p&&!GetNxt(O[p].S[x])) O[p].S[x]=now,p=O[p].F;
if(!p) return Co(now,1),F5(now,1);
if(O[p].L+1==O[q=O[p].S[x]].L) return Co(now,q),F5(now,1);
O[k=TwoP[id]=++tot]=O[q],O[k].L=O[p].L+1,O[k].Sz=0,O[k].Ex=1,O[k].Nxt=q,
F5(q,-1),Co(now,k),Co(q,k),F5(q,1),F5(k,1),F5(now,1);//删除原来的贡献,更新新的贡献
W(p&&!(GetNxt(O[p].S[x])^q)) O[p].S[x]=k,p=O[p].F;
}
I void Delete(CI x) {O[x].Ex=0,F5(x,O[x].Sz-1),O[GetFa(x)].Sz+=O[x].Sz-1;}//删除字符
I LL GetAns() {return ans;}//求答案
}S;
int main()
{
RI i,len,x,T=0;for(scanf("%s",s+1),len=strlen(s+1),i=1;i<=len;++i)
{
if(s[i]^'-') S.Insert(s[i]&31,i,S.SamP[S.ExSt[T]]),S.ExSt[++T]=i;//加入字符,更新栈
else//删除字符
{
S.TwoP[S.ExSt[T]]&&(S.Delete(S.TwoP[S.ExSt[T]]),0),//若插入了两个节点,则删除第二个插入的节点
S.Delete(S.SamP[S.ExSt[T]]),--T;//删除第一个插入的节点
}printf("%lld
",S.GetAns());//输出答案
}return 0;
}