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


    题目

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


    思考历程

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


    正解

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


    代码

    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
    ",ans);
    	return 0;
    }
    

    总结

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

  • 相关阅读:
    通过唯一ID实现简单的日志跟踪实现
    从零单排入门机器学习:Octave/matlab的经常使用知识之矩阵和向量
    zoj 1671 Walking Ant
    JDBC基础
    Android从源码看ListView的重用机制
    JavaScript设计模式 Item9 --适配器模式Adapter
    C++11新特性之 std::forward(完美转发)
    [组合数]求组合数的几种方法总结
    HDU 4005 The war(双连通好题)
    Workspace in use or cannot be created, choose a different one.--错误解决的方法
  • 原文地址:https://www.cnblogs.com/jz-597/p/11183890.html
Copyright © 2020-2023  润新知