[JZOJ3402] 【GDOI2014模拟】Pty的字符串

题目

给你一棵每条边从父亲指向儿子的树,每条边上面有一个字母。
从树上的任意一点出发,走出的路径就是对应一个子串。
(这不是 T r i e Trie Trie,因为每个父亲可能会连出字母相同的边)
再给你一个字符串 S S S,让你求 S S S的子串和树上路径的对应个数。


思考历程

一开始以为路径是从根节点出发,于是我就想,这难道不是一个AC自动机的裸题吗?
啪啪啪地就把AC自动机打了上去……
然后发现样例过不去……
于是终于理解完题目大意,开始死磕。
但是一直都没有放弃AC自动机的做法。
一波乱搞后,我终于爆0了。


正解

后来才意识到其实这题是后缀自动机。
于是就开始自己刚后缀自动机的做法了……
大体思路是处理出树上的每个子串的出现次数,然后跟 S S S的子串进行匹配。
先想想前面的这个问题。
首先,由于这是一棵树,所以就对整棵树建广义后缀自动机
至于这是什么东西……我想就不该在这里赘述了。
然后有个点 i i i从树的根部往下跑。跑的时候 r o o t root root i i i路径所组成的字符串的所有后缀统计进来
i i i在跑的时候也有个点 t t t在后缀自动机上跑。对于每对 ( i , t ) (i,t) (i,t),就将 t t t f a i l fail fail链上的所有统计次数 n u m + 1 num+1 num+1
显然,由于节点所表示的子串的 r i g h t right right集合相同,所以 r i g h t right right集合大小相同,所以它们的出现次数也相同。所以 n u m num num表示的是这个节点的所有子串各自出现的次数。
在实现的时候,可以先在 t t t点打个标记,跑完之后 f a i l fail fail树上标记上传就好了(如果你喜欢就打树链剖分吧~~(手动滑稽))。
这样我们就处理出树上的每个子串的出现次数了,接下来是匹配的问题。
也是枚举个 i i i表示子串的右边界,也有个 t t t在后缀自动机上面跳。
l l l为以 i i i为结尾的在树中出现过的最长的子串长度。
显然, t t t在跳的过程中保证了 t m i n l e n ≤ l ≤ t m a x l e n t_{minlen}\leq l\leq t_{maxlen} tminlenltmaxlen
由于子串 S i − l + 1.. i S_{i-l+1..i} Sil+1..i的所有后缀都要记录进答案中,而 t t t f a i l fail fail链上所有节点表示的子串都是它的后缀,所以就将 f a i l fail fail链上的出现次数全部加上(即为累加 n u m ∗ ( m a x l e n − m i n l e n + 1 ) num*(maxlen-minlen+1) num(maxlenminlen+1))。具体实现的时候可以用个前缀和将祖先的出现次数全部存下来,记作 s u m sum sum
我们计算的是长度在 [ 1 , l ] [1,l] [1,l]的后缀,可以拆成 [ 1 , t m i n l e n − 1 ] [1,t_{minlen}-1] [1,tminlen1] [ t m i n l e n , l ] [t_{minlen},l] [tminlen,l]这两段,前者的答案是 t f a i l s u m t_{fail_{sum}} tfailsum,后者的答案为 t n u m ( l − t f a i l m a x l e n ) t_{num}(l-t_{fail_{maxlen}}) tnum(ltfailmaxlen),加起来就行了。
然后问题就变成了求 l l l的具体值。
这实际上很简单,只不过我之前想了很久,甚至恨不得再打一个AC自动机来求。
在一开始的时候 l = 0 l=0 l=0。后来,当 t t t可以往前走的时候, l l l的值也相应地 + 1 +1 +1。当 t t t要跳 f a i l fail fail的时候,就将 l l l t f a i l m a x l e n t_{fail_{maxlen}} tfailmaxlen取个最小值。具体原因不再赘述。
于是这道题就没了……


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 800010
#define LEN 8000010
int n;
struct EDGE{
	int to,c;
	EDGE *las;
} e[N];
int ne;
EDGE *last[N];
struct Node{
	Node *c[3],*fail;
	int len;
	int num;
	long long sum;
} d[N*2];
int cnt;
Node *S,*T;
Node *mp[N];
inline void insert(int ch){
	if (T->c[ch] && T->c[ch]->len==T->len+1){
		T=T->c[ch];
		return;
	}
	Node *nw=&d[++cnt],*p,*q;
	nw->len=T->len+1;
	for (p=T;p && !p->c[ch];p=p->fail)
		p->c[ch]=nw;
	if (!p)
		nw->fail=S;
	else{
		q=p->c[ch];
		if (p->len+1==q->len)
			nw->fail=q;
		else{
			Node *clone=&d[++cnt];
			memcpy(clone,q,sizeof(Node));
			clone->len=p->len+1;
			for (;p && p->c[ch]==q;p=p->fail)
				p->c[ch]=clone;
			nw->fail=q->fail=clone;
		}
	}
	T=nw;
}
inline void build(){
	static int q[N];
	T=S=&d[++cnt];
	int head=0,tail=1;
	q[1]=1;
	mp[1]=S;
	do{
		int x=q[++head];
		for (EDGE *ei=last[x];ei;ei=ei->las){
			T=mp[x];
			insert(ei->c);
			mp[ei->to]=T;
			q[++tail]=ei->to;
		}
	}
	while (head!=tail);
}
inline void hang(){
	static pair<int,Node*> q[N];
	int head=0,tail=1;
	q[1]={1,S};
	do{
		++head;
		int x=q[head].first;
		Node *ht=q[head].second;
		for (EDGE *ei=last[x];ei;ei=ei->las){
			Node *tt=ht;
			while (tt!=S && !tt->c[ei->c])
				tt=tt->fail;
			if (tt->c[ei->c])
				tt=tt->c[ei->c];
			tt->num++;
			q[++tail]={ei->to,tt};
		}
	}
	while (head!=tail);
}
Node *q[N];
bool vis[N];
inline void get_sum(){
	int head=0,tail=1;
	q[1]=S;
	vis[1]=1;
	do{
		Node *x=q[++head];
		for (int i=0;i<3;++i)
			if (x->c[i] && !vis[x->c[i]-d]){
				vis[x->c[i]-d]=1;
				q[++tail]=x->c[i];
			}
	}
	while (head!=tail);
	for (int i=tail;i>=2;--i){
		Node *x=q[i];
		x->fail->num+=x->num;
	}
	for (int i=2;i<=tail;++i){
		Node *x=q[i];
		x->sum=x->fail->sum+1ll*x->num*(x->len-x->fail->len);
	}
}
char s[LEN];
int main(){
	scanf("%d",&n);
	for (int i=2;i<=n;++i){
		int fa;
		char c[2];
		scanf("%d%s",&fa,c);
		e[ne]={i,*c-'a',last[fa]};
		last[fa]=e+ne++;
	}
	build(),hang(),get_sum();
	Node *t=S;
	scanf("%s",s);
	long long ans=0;
	int len=0;
	for (char *ch=s;*ch;++ch){
		while (t!=S && !t->c[*ch-'a']){
			t=t->fail;
			len=min(len,t->len);
		}
		if (t->c[*ch-'a']){
			t=t->c[*ch-'a'];
			len++;
		}
		if (t!=S)
			ans+=t->fail->sum+1ll*t->num*(len-t->fail->len);
	}
	printf("%lld\n",ans);
	return 0;
}

总结

这也算是一道后缀自动机的模板题吧……
看来还是不够熟练啊……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值