• 51nod1600Simple KMP【SAM,树链剖分】


    正题

    题目链接:http://www.51nod.com/Challenge/Problem.html#problemId=1600


    题目大意

    给出一个字符串\(s\),每次在最后插入一个字符后求它的所有分别子串构出的\(fail\)树的深度和。

    \(1\leq Q\leq 10^5\)


    解题思路

    考虑两个相等的子串长度为\(len\),那么以后面那个子串末尾结尾的\(fail\)\(len\)种左端点的情况是指向前面那个子串的。

    新插入后所有串的后缀都是新的子串,考虑如何统计这些串的答案,首先不考虑最后一个位置那么深度和就是前面那次新加的深度和。现在只需要计算新插入那个字符在这\(n\)个串中的贡献,我们可以找出所有和这些串的所有后缀相同的子串都会产生贡献,这个可以用\(SAM\)统计。

    所以可以考虑先把完整的串的\(SAM\)建出来再考虑做法,每次插入一个字符串的时候先查询它在\(parents\)树上到根的路径的边权乘上边的长度和,然后再向这条路径上每条边的权值加一。

    注意到要路径加权求和,所以要加一个树剖就可以了

    时间复杂度\(O(n\log^2 n)\)


    code

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define ll long long
    using namespace std;
    const int N=4e5+10,P=1e9+7;
    struct node{
    	int to,next;
    }a[N];
    int n,cnt,last,tot,dfc,p[N],ls[N];
    int siz[N],son[N],top[N],dfn[N],rfn[N];
    int fa[N],ch[N][26];ll len[N];
    char s[N];bool v[N];
    struct SegTree{
    	ll w[N<<2],lazy[N<<2];
    	void Downdata(int x,int L,int R){
    		if(!lazy[x])return;
    		int mid=(L+R)>>1;
    		w[x*2]=(w[x*2]+lazy[x]*(len[dfn[mid]]-len[dfn[L-1]]))%P;
    		w[x*2+1]=(w[x*2+1]+lazy[x]*(len[dfn[R]]-len[dfn[mid]]))%P;
    		lazy[x*2]+=lazy[x];lazy[x*2+1]+=lazy[x];
    		lazy[x]=0;return;
    	}
    	void Change(int x,int L,int R,int l,int r){
    		if(L==l&&R==r){
    			(w[x]+=len[dfn[R]]-len[dfn[L-1]])%=P;
    			lazy[x]++;return;
    		}
    		int mid=(L+R)>>1;Downdata(x,L,R);
    		if(r<=mid)Change(x*2,L,mid,l,r);
    		else if(l>mid) Change(x*2+1,mid+1,R,l,r);
    		else Change(x*2,L,mid,l,mid),Change(x*2+1,mid+1,R,mid+1,r);
    		w[x]=(w[x*2]+w[x*2+1]);
    		return;
    	}
    	ll Ask(int x,int L,int R,int l,int r){
    		if(L==l&&R==r)return w[x];
    		int mid=(L+R)>>1;Downdata(x,L,R);
    		if(r<=mid)return Ask(x*2,L,mid,l,r);
    		if(l>mid)return Ask(x*2+1,mid+1,R,l,r);
    		return (Ask(x*2,L,mid,l,mid)+Ask(x*2+1,mid+1,R,mid+1,r))%P;
    	}
    }T;
    void Insert(int c){
    	int p=last,np=last=++cnt;
    	len[np]=len[p]+1;
    	for(;p&&!ch[p][c];p=fa[p])ch[p][c]=np;
    	if(!p)fa[np]=1;
    	else{
    		int q=ch[p][c];
    		if(len[q]==len[p]+1)fa[np]=q;
    		else{
    			int nq=++cnt;len[nq]=len[p]+1;
    			memcpy(ch[nq],ch[q],sizeof(ch[nq]));
    			fa[nq]=fa[q];fa[q]=fa[np]=nq;
    			for(;ch[p][c]==q;p=fa[p])ch[p][c]=nq;
    		}
    	}
    	v[np]=1;return;
    }
    void addl(int x,int y){
    	a[++tot].to=y;
    	a[tot].next=ls[x];
    	ls[x]=tot;return;
    }
    void dfs(int x){
    	siz[x]=1;
    	for(int i=ls[x];i;i=a[i].next){
    		int y=a[i].to;
    		dfs(y);siz[x]+=siz[y];
    		len[y]=len[y]-len[x];
    		if(siz[y]>siz[son[x]])son[x]=y;
    	}
    	return;
    }
    void dfs2(int x){
    	dfn[++dfc]=x;rfn[x]=dfc;
    	if(son[x]){
    		top[son[x]]=top[x];
    		dfs2(son[x]);
    	}
    	for(int i=ls[x];i;i=a[i].next){
    		int y=a[i].to;
    		if(y==son[x])continue;
    		top[y]=y;dfs2(y);
    	}
    	return;
    }
    void print(int x)
    {if(x>9)print(x/10);putchar(x%10+48);return;}
    signed main()
    {
    	freopen("string.in","r",stdin);
    	freopen("string.out","w",stdout);
    	scanf("%d",&n);
    	scanf("%s",s+1);last=cnt=1;
    	for(int i=1;i<=n;i++)
    		Insert(s[i]-'a'),p[i]=last;
    	for(int i=2;i<=cnt;i++)addl(fa[i],i);
    	top[1]=1;dfs(1);dfs2(1);
    	ll k=0,ans=0;
    	for(int i=1;i<=cnt;i++)
    		len[dfn[i]]=(len[dfn[i]]+len[dfn[i-1]])%P;
    	for(int i=1;i<=n;i++){
    		int x=p[i];
    		while(x){
    			k=(k+T.Ask(1,1,cnt,rfn[top[x]],rfn[x]))%P;
    			x=fa[top[x]];
    		}
    		ans=(ans+k)%P;x=p[i];
    		while(x){
    			T.Change(1,1,cnt,rfn[top[x]],rfn[x]);
    			x=fa[top[x]];
    		}
    		print((ans+P)%P);
    		putchar('\n');
    	}
    	return 0;
    }
    
  • 相关阅读:
    for循环删除数组中的元素crash问题
    iOS判断字符串中含不含有汉字
    iOS 拨打电话(解决openURL延迟和不同方法比较)
    ios oc单例宏定义
    iOS UIBezierPath简单实用
    iOS视图切割圆角
    iOS 内购集成与遇到的坑,添加新内购项目
    iOS工程中创建pch文件
    四舍五入的方法
    ScrollView定时器复用
  • 原文地址:https://www.cnblogs.com/QuantAsk/p/14421228.html
Copyright © 2020-2023  润新知