20200721 T2 s2mple【后缀自动机妙用】

题目描述

在这里插入图片描述
n , Q ≤ 4 ∗ 1 0 5 n,Q\le4*10^5 n,Q4105

题目分析

S S S 的 SAM。记询问串为 T T T

要数 S S S 中本质不同的串中 T T T 的出现次数之和

相当于给 T T T 后面接上一个串 R R R,前面接上一个串 L L L L T R LTR LTR 出现在 S S S 中,求 L , R L,R L,R 的对数。

假设已经确定了 T R = p TR=p TR=p,考虑怎么数 L L L:以 p p p 为后缀的串,就是在 p p p 对应 fail 树上节点 q q q 子树中的串,加上 q q q 剩下长度对应的串。可以表示为 s u b t r e e [ q ] − ( ∣ p ∣ − l e n f a i l q − 1 ) subtree[q]-(|p|-len_{fail_q}-1) subtree[q](plenfailq1),其中 s u b t r e e [ q ] subtree[q] subtree[q] 表示子树中的点的 l e n − l e n f a i l len-len_{fail} lenlenfail 之和。

然后考虑对所有的 R R R 统计上面的贡献。给 T T T 后面加字符,相当于在 SAM 的 DAG 上走,那么将 ∣ p ∣ |p| p 看做未知量,在 DAG 上做 DP,求出 i i i 点带入长度为 x x x 的串后往后走能够产生的贡献和,表示为 a x + b ax+b ax+b,那么每走一步, a x + b → a ( x + 1 ) + b = a x + a + b ax+b\rarr a(x+1)+b=ax+a+b ax+ba(x+1)+b=ax+a+b

所以转移就是 a [ i ] = − 1 + ∑ a [ j ] ,   b [ i ] = s u b t r e e [ i ] + l e n f a i l i + 1 + ∑ a [ j ] + b [ j ] a[i]= -1+\sum a[j],~b[i]=subtree[i]+len_{fail_i}+1+\sum a[j]+b[j] a[i]=1+a[j], b[i]=subtree[i]+lenfaili+1+a[j]+b[j]

每次询问时倍增找到 T T T 对应节点 t t t,输出 a [ t ] ∗ ∣ T ∣ + b [ t ] a[t]*|T|+b[t] a[t]T+b[t] 即可。

Code:

#include<bits/stdc++.h>
#define maxn 800005
#define LL long long
using namespace std;
char cb[1<<20],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<20,stdin),cs==ct)?0:*cs++)
void read(int &a){
	char c;while(!isdigit(c=getc()));
	for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');
}
int n,Q,pos[maxn],f[maxn][20];
char s[maxn];
vector<int>G[maxn];
LL sub[maxn],a[maxn],b[maxn];
int fa[maxn]={-1},ch[maxn][26],len[maxn],last,sz;
void extend(int c){
	int cur=++sz,p=last,q; len[last=cur]=len[p]+1;
	for(;~p&&!ch[p][c];p=fa[p]) ch[p][c]=cur;
	if(p==-1) fa[cur]=0;
	else if(len[q=ch[p][c]]==len[p]+1) fa[cur]=q;
	else{
		int clone=++sz; len[clone]=len[p]+1,fa[clone]=fa[q];
		memcpy(ch[clone],ch[q],sizeof ch[q]),fa[q]=fa[cur]=clone;
		for(;~p&&ch[p][c]==q;p=fa[p]) ch[p][c]=clone;
	}
}
int main()
{
	freopen("s2mple.in","r",stdin);
	freopen("s2mple.out","w",stdout);
	scanf("%d%d%s",&n,&Q,s+1);
	for(int i=1;i<=n;i++) extend(s[i]-'a'),pos[i]=last;
	for(int i=1;i<=sz;i++) G[len[i]].push_back(i);
	//cerr<<sz<<endl;
	for(int i=sz;i>=1;i--) for(int x:G[i]){
		int f=fa[x];
		sub[x]+=len[x]-len[f],sub[f]+=sub[x];
		a[x]=-1,b[x]=sub[x]+len[f]+1;
		for(int j=0,y;j<26;j++) if(y=ch[x][j]) a[x]+=a[y],b[x]+=a[y]+b[y];
	}
	for(int i=1;i<=sz;i++) f[i][0]=fa[i];
	for(int j=1;j<=19;j++) for(int i=1;i<=sz;i++) f[i][j]=f[f[i][j-1]][j-1];
	for(int l,r,x;Q--;){
		read(l),read(r),x=pos[r];
		for(int i=19;i>=0;i--) if(len[f[x][i]]>=r-l+1) x=f[x][i];
		printf("%lld\n",a[x]*(r-l+1)+b[x]);
	}
}

字符串集合只有 ab 的话,大样例节点数卡到了 799969,所以实现不好的话后缀自动机的复杂度还是要乘上字符集大小的。。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值