Check,Check,Check one two!,洛谷P5115,SAM fail树上合并答案

本文介绍了如何通过前缀自动机(Suffix Automaton, SAM)快速计算文本中点对的贡献,特别关注了k1和k2限制下的等差数列求和问题。作者的方法独特,以90ms的高效运行时间解决挑战,核心在于基数排序处理SAM节点合并和贡献计算。

首先我们考虑对于一个点对(i,j)(i,j)(i,j)来说,要找到前面极长的匹配aaa,后面极长的匹配bbb,贡献是两者长度的乘积(a+1)∗(b+1)(a+1)*(b+1)(a+1)(b+1),我们考虑在SAM上(i+p,j+p),p∈[0,b](i+p,j+p),p\in[0,b](i+p,j+p),p[0,b]两个endposendposendpos合并时统计(i,j)(i,j)(i,j)的贡献。
由于后缀自动机上两个endposendposendposLCALCALCAmaxlenmaxlenmaxlen就是对应两个前缀的最长公共后缀,所以我们可以肯定的是,当(i+p,j+p)(i+p,j+p)(i+p,j+p)合并时,设对应点的maxlenmaxlenmaxlenccc(i+p−c+1,j+p−c+1)=(i−a,j−a)(i+p-c+1,j+p-c+1)=(i-a,j-a)(i+pc+1,j+pc+1)=(ia,ja)
(i,j)(i,j)(i,j)会被b+1b+1b+1种点对统计到,我们只需要在每一次被统计到时给答案加上a+1a+1a+1就可以了。在SAMSAMSAM节点上,贡献和相当于每次加一个等差数列。
这时候没有k1,k2k1,k2k1,k2限制的答案已经出来了。
考虑上k2k2k2的限制,我们发现只要限制一下每次加的等差数列的末项不大于k2k2k2就可以了。
k1k1k1的限制怎么办,对于一个SAMSAMSAM上的节点,我们看是否存在被恰好计算第k1+1k1+1k1+1次的节点,如果存在,那么减去其产生的所有贡献。同时,这个还限制了等差数列的首项不小于maxleni−k1+1maxlen_i-k1+1maxlenik1+1。具体可以看看代码怎么写。
好像我的做法与其他题解不大相同,同时也以90ms90ms90ms的运行时间成为了题解提交时间为止的最优解。
代码十分简洁明了,个人在处理SAMSAMSAMfailfailfail树合并问题时钟爱对长度基数排序。

#include<bits/stdc++.h>
using namespace std;

const int N=200010;
int fail[N],ch[N][26],mx[N],n,k1,k2,las,rig[N],sum[N],a[N],tot;
unsigned long long op[N],ans;
char s[N];

void insert(int c){
	int x=++tot,p=las;las=x;mx[x]=mx[p]+1;rig[x]=1;
	for(;p!=-1 && !ch[p][c];p=fail[p]) ch[p][c]=x;
	if(p==-1) fail[x]=0;
	else if(mx[ch[p][c]]==mx[p]+1) fail[x]=ch[p][c];
	else{
		int q=++tot,tmp=ch[p][c];mx[q]=mx[p]+1;
		fail[q]=fail[tmp];fail[x]=fail[tmp]=q;
		for(int i=0;i<26;i++) ch[q][i]=ch[tmp][i];
		for(;p!=-1 && ch[p][c]==tmp;p=fail[p]) ch[p][c]=q;
	}
}

unsigned long long gs(int x,int y){
	if(x>y) return 0;
	return 1ull*(x+y)*(y-x+1)/2;
}

int main(){
	scanf("%s",s+1);n=strlen(s+1);
	scanf("%d %d",&k1,&k2);fail[0]=-1;
	for(int i=1;i<=n;i++) insert(s[i]-'a');
	for(int i=1;i<=tot;i++) sum[mx[i]]++;
	for(int i=n;i>=1;i--) sum[i]+=sum[i+1];
	for(int i=1;i<=tot;i++) a[sum[mx[i]]--]=i;
	for(int i=1;i<=tot;i++){
		int x=a[i];
		op[fail[x]]+=1ull*rig[x]*rig[fail[x]];
		rig[fail[x]]+=rig[x];
		ans+=op[x]*gs(max(1,mx[x]-k1+1),min(k2,mx[x]));
		if(mx[x]>k1 && mx[x]-k1<=k2) ans-=1ull*op[x]*(mx[x]-k1)*k1;
	}
	printf("%llu\n",ans);
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值