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


    咕了半个暑假的题目

    Description

    • 有一棵树有n个节点,深度浅的点往深度深的点连有向边,边上带有字符ch(a~c)
    • 给定字符串S,设一条从任意点出发往下的树上路径对应边上字符连接起来的字符串,求S的所有子串对应树上字符串的总匹配数和。
    • |S|<=8e6,n<=8e5

    Solution

    • 关于子串问题,不难想到后缀自动机,刚开始我想以S建一个SAM,结果发现对于从任意点出发并不能很好地解决(其实是我刚开始以为只从根节点出发,看错了题意)。
    • 相反的,我们有另一种用后缀自动机的方法,就是以这个树建一个广义后缀自动机.
    • 广义后缀自动机看似很高深,实际上就是后缀自动机在树上的版本,而具体实现也很简单,在加入一个节点时将last变成它的父亲的加入时的SAM上节点的编号。
    • 我们遍历S,假设当前到i,我们要求S中以位置i结尾的子串在树上的出现次数。
    1. 任意子串在树上的出现次数: 遍历这棵树,对节点x构造SAM的时候得到x在SAM上的节点编号,显然从根到当前节点的字符串就是这个节点的字符串,而fail链上的所有后缀都在这里出现了一次,所以要将fail链上的节点出现次数(假设是num)+1。实现时打个tropu转移。
    2. 任意子串后缀的总出现次数,找节点,把fail链上的num*(maxlen-minlen+1)相加,一个前缀和。
    • 在广义SAM中相对应地找到以i为结尾最长的串的节点,因为并不是以i结尾的所有串都出现过,我们在寻找这个节点的时候要是没有对应字符出现我们就要跳fail,所以我们要记录一个L,表示以i结尾最长出现在树中的长度。
    • 显然如果当前SAM节点是x,tr[x].minlen<=L<=tr[x].maxlen。
    • 这个长度L的子串后缀的总出现次数,由于L并不完全代表这个节点,所以完整的只有tr[x].fail的前缀和,然后将剩下的长度minlen~L的补上,也就是num[x]*(L-minlen+1)。
    • 时间复杂度O(n+|S|)
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    #include<cstring>
    #define maxn 800005
    #define maxm 8000005
    #define maxt 2000000
    #define ll long long 
    using namespace std;
    
    int n,i,j,k;
    int em,e[maxn],nx[maxn],ls[maxn],ec[maxn];
    char ch;
    
    void insert(int x,int y,int z){
    	em++; e[em]=y; nx[em]=ls[x]; ls[x]=em; ec[em]=z;
    }
    
    struct node{
    	int len,fa,to[3];
    } tr[maxt];
    ll num[maxt],sum[maxt],ans;
    int rt,tot,Las[maxn];
    int add(int x,int las){
    	int p=las,np=++tot; 
    	num[np]++;
    	tr[np].len=tr[p].len+1;
    	for(;p&&!tr[p].to[x];p=tr[p].fa) tr[p].to[x]=np;
    	if (!p) tr[np].fa=rt; else{
    		int q=tr[p].to[x];
    		if (tr[p].len+1==tr[q].len) tr[np].fa=q; else {
    			int nq=++tot;
    			tr[nq]=tr[q];
    			tr[nq].len=tr[p].len+1;
    			tr[np].fa=tr[q].fa=nq;
    			for(;p&&tr[p].to[x]==q;p=tr[p].fa) tr[p].to[x]=nq;
    		}
    	}
    	return np;
    }
    
    void DFS(int x){
    	for(int i=ls[x];i;i=nx[i]){
    		Las[e[i]]=add(ec[i],Las[x]);
    		DFS(e[i]);
    	}
    }
    
    int du[maxt],t,w,d[maxt];
    void Tropu(){
    	for(i=1;i<=tot;i++) if (tr[i].fa) 
    		du[tr[i].fa]++;
    	t=w=0;
    	for(i=1;i<=tot;i++) if (!du[i]) d[++w]=i;
    	while (t<w){
    		int x=d[++t],y=tr[x].fa;
    		if (x==rt) break;
    		num[y]+=num[x];
    		if (!--du[y]) d[++w]=y;
    	}
    	for(j=w;j>=1;j--) {
    		i=d[j];
    		if (tr[i].fa) sum[i]=num[i]*(tr[i].len-tr[tr[i].fa].len)+sum[tr[i].fa];
    	}
    }
    
    void doit(){
    	for(ch=getchar();ch<'a'||ch>'c';ch=getchar());
    	int x=rt,l=0,y;
    	for(;ch>='a'&&ch<='c';ch=getchar()){
    		k=ch-'a';
    		while (x&&!tr[x].to[k]) x=tr[x].fa,l=min(l,tr[x].len);
    		if (!x) x=rt,l=0; else {
    			x=tr[x].to[k],l++;
    			y=tr[x].fa;
    			ans+=sum[y]+1ll*(l-tr[y].len)*num[x];
    		}
    	}	
    }
    
    int main(){
    	scanf("%d",&n);
    	for(i=2;i<=n;i++){
    		scanf("%d",&k);
    		ch=getchar();
    		insert(k,i,getchar()-'a');
    	}
    	Las[1]=rt=tot=1;
    	DFS(1);
    	Tropu();
    	doit();
    	printf("%lld",ans);
    } 
    
  • 相关阅读:
    诚意租房网blog2
    3个微信小程序体验报告
    微信小程序正式上线 可置于聊天窗口顶部
    微信小程序开发工具测评
    死磕微信!支付宝版“小程序”曝光
    微信小程序开发—快速掌握组件及API的方法
    【微信小程序开发•系列文章六】生命周期和路由
    换个角度看微信小程序[推荐]
    微信小程序开发日记——高仿知乎日报(下)
    微信小程序开发日记——高仿知乎日报(中)
  • 原文地址:https://www.cnblogs.com/DeepThinking/p/11700917.html
Copyright © 2020-2023  润新知