[51nod1600][树链剖分][后缀自动机]Simple KMP

11 篇文章 0 订阅
8 篇文章 0 订阅

Description

对于一个字符串|S|,我们定义fail[i],表示最大的x使得S[1…x]=S[i-x+1…i],满足(x<i)
显然对于一个字符串,如果我们将每个0<=i<=|S|看成一个结点,除了i=0以外i向fail[i]连边,这是一颗树的形状,根是0
我们定义这棵树是G(S),设f(S)是G(S)中除了0号点以外所有点的深度之和,其中0号点的深度为-1
定义key(S)等于S的所有非空子串S’的f(S’)之和
给定一个字符串S,现在你要实现以下几种操作:
1.在S最后面加一个字符
2.询问key(S)
善良的出题人不希望你的答案比long long大,所以你需要将答案对1e9+7取模

Input

第一行一个正整数Q
Q<=10^5
第二行一个长度为Q的字符串S

Output

输出Q行,第i行表示前i个字符组成的字符串的答案

Sample Input

5
abaab

Sample Output

0
0
1
4
9

题解

字符串算法题都是一个套路qwq??
我们思考对于一个串,怎么算答案
对于这个串的某两个相同子串,设较后的那个子串的结束位置是 u u u
那么这两个子串能给答案贡献 n − u + 1 n-u+1 nu+1
同时再注意一个东西,我们把串从 i + 1 i+1 i+1缩小到 i i i的规模时,减小的其实就是相同的子串的数量
也就是把上面那个东西的贡献看成 1 1 1
所以,如果我们能够维护这个东西,即维护两种不同贡献的相同子串数
那么就可以从最后一个答案递推回第一个答案
我们离线把这个串的SAM和parent树建出来
考虑当前最后一个位置在parent树上的状态是什么,设他为 x x x
那么对于他的某个祖先 y y y,对于 y y y的另外一棵子树中的某个状态 z z z,显然, x x x z z z的最长公共后缀就是 m x ( y ) mx(y) mx(y),那么这两个状态能提供的相同子串就是 m x ( y ) mx(y) mx(y)
可知贡献都在一条链上,考虑树剖维护子树信息
每个点维护其轻儿子子树中 r i g h t right right集合的大小与其的 m x ( i ) mx(i) mx(i)的乘积,遇到重链的时候直接计算,轻链的时候暴力计算轻链父亲的贡献,那么复杂度就是 n l o g 2 n nlog^2n nlog2n
删除一个点时只需要删除他到根上的轻链的父亲的贡献,这个也可以做到
那么一个点的贡献就可以计算出来了,又因为每次都是删除最后一个位置,所以不需要考虑系数
计算最后一个答案同理,按上面的过程模拟即可
复杂度 n l o g 2 n nlog^2n nlog2n
SAM和parent树真是个好玩的东西啊

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<ctime>
#include<map>
#include<bitset>
#include<set>
#define LL long long
#define mp(x,y) make_pair(x,y)
#define pll pair<long long,long long>
#define pii pair<int,int>
#define lc now<<1
#define rc now<<1|1
using namespace std;
inline int read()
{
	int f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int stack[20];
inline void write(int x)
{
	if(x<0){putchar('-');x=-x;}
    if(!x){putchar('0');return;}
    int top=0;
    while(x)stack[++top]=x%10,x/=10;
    while(top)putchar(stack[top--]+'0');
}
inline void pr1(int x){write(x);putchar(' ');}
inline void pr2(int x){write(x);putchar('\n');}
const int MAXN=200005;
const int mod=1e9+7;
char ch[MAXN];
struct SAM{int son[27],dep,parent;}tr[MAXN];int root,cnt,lst;
void add(int x)
{
	int np=++cnt,p=lst;
	tr[np].dep=tr[p].dep+1;
	while(p&&!tr[p].son[x])tr[p].son[x]=np,p=tr[p].parent;
	if(!p)tr[np].parent=root;
	else
	{
		int q=tr[p].son[x];
		if(tr[q].dep==tr[p].dep+1)tr[np].parent=q;
		else
		{
			int nq=++cnt;tr[nq]=tr[q];
			tr[nq].dep=tr[p].dep+1;
			tr[q].parent=tr[np].parent=nq;
			while(p&&tr[p].son[x]==q)tr[p].son[x]=nq,p=tr[p].parent;
		}
	}
	lst=np;
}
struct edge{int x,y,next;}a[2*MAXN];int len,last[MAXN];
void ins(int x,int y){len++;a[len].x=x;a[len].y=y;a[len].next=last[x];last[x]=len;}

int son[MAXN],tot[MAXN],dep[MAXN],fa[MAXN],top[MAXN],ys[MAXN],n,m,z;
int in[MAXN],ot[MAXN],dfn;
void pre_tree_node(int x)
{
	in[x]=++dfn;tot[x]=1;
	for(int k=last[x];k;k=a[k].next)
	{
		int y=a[k].y;
		fa[y]=x;dep[y]=dep[x]+1;
		pre_tree_node(y);
		if(tot[y]>tot[son[x]])son[x]=y;
		tot[x]+=tot[y];
	}
	ot[x]=dfn;
}
int ri[MAXN];
void pre_tree_edge(int x,int tp)
{
	ys[x]=++z;top[x]=tp;
	if(son[x])pre_tree_edge(son[x],son[x]);
	for(int k=last[x];k;k=a[k].next)
		if(a[k].y!=son[x])pre_tree_edge(a[k].y,a[k].y);
}
void ad(int &x,int y){x+=y;if(x>=mod)x-=mod;}
void dl(int &x,int y){x-=y;if(x<0)x+=mod;}
int CA1[MAXN],CA2[MAXN],le[MAXN];
struct segtree
{
	int cal[MAXN*4],lazy[MAXN*4],sum[MAXN*4];//这一段的mx-mn值 
	void buildtree1(int now,int l,int r)
	{
		if(l==r){sum[now]=CA2[l];return ;}
		int mid=(l+r)/2;
		buildtree1(lc,l,mid);buildtree1(rc,mid+1,r);
	}
	void buildtree(int now,int l,int r)
	{
		lazy[now]=0;
		if(l==r){cal[now]=CA1[l];sum[now]=CA2[l];return ;}
		int mid=(l+r)/2;
		buildtree(lc,l,mid);buildtree(rc,mid+1,r);
	}
	void down(int now)
	{
		if(!lazy[now])return ;
		ad(cal[lc],1LL*lazy[now]*sum[lc]%mod);ad(cal[rc],1LL*lazy[now]*sum[rc]%mod);
		ad(lazy[lc],lazy[now]);ad(lazy[rc],lazy[now]);
		lazy[now]=0;
	}
	void modify(int now,int l,int r,int ql,int qr,int c)
	{
		if(l==ql&&r==qr){ad(cal[now],1LL*c*sum[now]%mod);ad(lazy[now],c);return ;}
		int mid=(l+r)/2;down(now);
		if(qr<=mid)modify(lc,l,mid,ql,qr,c);
		else if(mid+1<=ql)modify(rc,mid+1,r,ql,qr,c);
		else modify(lc,l,mid,ql,mid,c),modify(rc,mid+1,r,mid+1,qr,c);
		cal[now]=(cal[lc]+cal[rc])%mod;
	}
	int qry(int now,int l,int r,int ql,int qr)
	{
		if(l==ql&&r==qr)return cal[now];
		int mid=(l+r)/2;down(now);
		if(qr<=mid)return qry(lc,l,mid,ql,qr);
		else if(mid+1<=ql)return qry(rc,mid+1,r,ql,qr);
		else return (qry(lc,l,mid,ql,mid)+qry(rc,mid+1,r,mid+1,qr))%mod;
	}
}seg;//维护的是除重儿子外的right的siz * 自身的mx-mn 


int s[MAXN];
int lowbit(int x){return x&-x;}
void modify(int x,int c){for(;x<=cnt;x+=lowbit(x))ad(s[x],c);}
int qry(int x){int ret=0;for(;x>=1;x-=lowbit(x))ad(ret,s[x]);return ret;}
int getcal(int u,int l,int r)//得到u子树中除了l~r一段的right的siz 
{
	if(!l)return qry(ot[u])-qry(in[u]-1);
	return (qry(ot[u])-qry(in[u]-1))-(qry(r)-qry(l-1));
}
int ans1,ans2,id[MAXN];//答案    后面一个串只算一次的答案 

int getsum(int x)
{
	int tx=top[x],re=0,lst=0;
	while(tx!=1)
	{
		if(x!=tx)ad(re,seg.qry(1,1,cnt,ys[tx],ys[fa[x]]));
		if(lst)
		{
			int sz=getcal(x,in[lst],ot[lst]);
			ad(re,1LL*le[x]*sz%mod);
		}
		lst=tx;x=fa[tx];tx=top[x];
	}
	if(x!=tx)ad(re,seg.qry(1,1,cnt,ys[tx],ys[fa[x]]));
	if(lst)
	{
		int sz=getcal(x,in[lst],ot[lst]);
		ad(re,1LL*le[x]*sz%mod);
	}
	return re;
}
void mdall(int x,int c)
{
	int tx=top[x];
	while(tx!=1)
	{
		seg.modify(1,1,cnt,ys[x],ys[x],c);
		x=fa[tx];tx=top[x];
	}
	seg.modify(1,1,cnt,ys[x],ys[x],c);
}
int answer[MAXN];
int main()
{
	n=read();root=cnt=lst=1;
	scanf("%s",ch+1);
	for(int i=1;i<=n;i++)add(ch[i]-'a'+1);
	for(int i=1;i<=cnt;i++)if(tr[i].parent)ins(tr[i].parent,i);
	pre_tree_node(1);
	pre_tree_edge(1,1);
	for(int i=1;i<=cnt;i++)le[i]=tr[i].dep;
	for(int i=1,p=root;i<=n;i++)
	{
		p=tr[p].son[ch[i]-'a'+1];
		ri[p]++;id[i]=p;
	}
	for(int i=1;i<=cnt;i++)
	{
		int u=getcal(i,in[son[i]],ot[son[i]]);
		CA2[ys[i]]=tr[i].dep-tr[tr[i].parent].dep;
		CA1[ys[i]]=1LL*u*CA2[ys[i]]%mod;
	}
	seg.buildtree1(1,1,cnt);
	for(int i=1;i<=n;i++)
	{
		int u=getsum(id[i]);
		ad(ans2,u);ad(ans1,1LL*u*(n-i+1)%mod);
		mdall(id[i],1);
		modify(in[id[i]],1);
	}
	seg.buildtree(1,1,cnt);
	for(int i=n;i>=1;i--)
	{
		answer[i]=ans1;
		modify(in[id[i]],-1);
		dl(ans1,ans2);
		int u=getsum(id[i]);
		dl(ans2,u);
		mdall(id[i],-1);
	}
	for(int i=1;i<=n;i++)pr2(answer[i]);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值