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;
}

posted on 2019-05-16 22:41  autoint  阅读(121)  评论(0编辑  收藏  举报

导航