BZOJ3756 Pty的字符串
Pty的字符串
在神秘的东方有一棵奇葩的树,它有一个固定的根节点(编号为1)。树的每条边上都是一个字符,字符为a,b,c中的一个,你可以从树上的任意一个点出发,然后沿着远离根的边往下行走,在任意一个节点停止,将你经过的边的字符依次写下来,就能得到一个字符串,例如:
在这棵树中我们能够得到的字符串是:
c, cb, ca, a, b, a
现在pty得到了一棵树和一个字符串S。如果S的一个子串[l,r]和树上某条路径所得到的字符串完全相同,则我们称这个子串和该路径匹配。现在pty想知道,S的所有子串和树上的所有路径的匹配总数是多少?
N<=800000
树的最大深度<=800000
分析
我们可以先对trie树建出广义SAM,然后维护一下right集合大小(注意right集合在广义SAM上的维护方式)。
然后把匹配穿往广义SAM上匹配,假设现在匹配到了x节点,那么x的所有祖先可以被匹配上,那么一个节点的贡献即为right[x]*(len-l[fa[x]])+sum[fa[x]]。其中len为匹配长度。
维护所有祖先的和,即sum[x]。
时间复杂度\(O(n+m)\)
代码
拓扑排序必须老实来。
co int N=8e5+1;
int last=1,tot=1,head[N]={0,1};
int ch[N*2][3],fa[N*2],len[N*2],siz[N*2];
void extend(int c){
int p=last,cur=last=++tot;
len[cur]=len[p]+1,siz[cur]=1;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=cur;
if(!p) fa[cur]=1;
else{
int q=ch[p][c];
if(len[q]==len[p]+1) fa[cur]=q;
else{
int clone=++tot;
memcpy(ch[clone],ch[q],sizeof ch[q]);
fa[clone]=fa[q],len[clone]=len[p]+1;
fa[cur]=fa[q]=clone;
for(;ch[p][c]==q;p=fa[p]) ch[p][c]=clone;
}
}
}
int n,m,deg[N*2],ord[N*2];
ll f[N*2],ans;
char s[N*10];
int main(){
read(n);
for(int i=2,x,y;i<=n;++i){
read(x);for(y=getchar();!isalpha(y);y=getchar());
last=head[x],extend(y-'a'),head[i]=last;
}
for(int i=1;i<=tot;++i) ++deg[fa[i]];
int L=1,R=0;
for(int i=1;i<=tot;++i)if(!deg[i]) ord[++R]=i;
for(int p;L<=R;++L){
p=ord[L];
siz[fa[p]]+=siz[p];
if(--deg[fa[p]]==0) ord[++R]=fa[p];
}
for(int i=tot;i;--i) {
int p=ord[i];
f[p]=f[fa[p]]+(ll)(len[p]-len[fa[p]])*siz[p];
}
// for(int i=1;i<=tot;++i)
// std::cerr<<i<<" f="<<f[i]<<std::endl;
scanf("%s",s+1),m=strlen(s+1);
for(int i=1,p=1,l=0;i<=m;++i){
s[i]-='a';
while(p&&!ch[p][s[i]]) p=fa[p],l=len[p]; // edit1: change l meanwhile
if(!p) p=1,l=0;
else p=ch[p][s[i]],++l,ans+=f[fa[p]]+(ll)(l-len[fa[p]])*siz[p];
}
printf("%lld\n",ans);
return 0;
}
静渊以有谋,疏通而知事。