#4247. 串

本文介绍了一种优化的子串计数算法,用于解决FJOI题目中的子串计数问题。该算法利用后缀数组、hash值预处理和SASASA思想,对字符串进行最小表示法映射,从而高效地计算本质不同的子串数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

内存限制:1024 MiB
时间限制:1000 ms

zzq 喜欢做傻吊题。

一天,zzq 正在和往常一样做一个傻吊题:输入一个串,输出它共有多少个本质不同的子串。这里 本质不同指的是长得不一样。由于这是一道 FJOI 题,字符只有 {A,C,G,T} 四种。zzq 写了一个后缀 数组,调了半小时通过了。zzq 感觉这个题没啥意思,应该加强一下。

f(S)f(S)f(S)SSS 的最小表示,具体地,把 SSS 中第一次出现的字母替换为 a,第二次出现的字母替换 为 b,等等。例如,f(f(f(AGACC)=) =)=abacc。题意仍然是输入一个串,输出它共有多少个本质不同的子串,但是现在两个串 AAABBB 本质不同定义为 f(A)f(A)f(A)f(B)f(B)f(B) 不同。

由于现在这个题没那么傻吊了,zzq 不会做了。他想请你解决这个问题。

n≤105n ≤ 10^5n105

题解

对于最小表示法,发现只有 242424 种映射关系,于是预处理出这 242424 种映射关系的 hashhashhash

对于每个后缀,可以找出它的映射关系

根据 SASASA 的思想,将所有后缀按照映射后的字典序排序,然后答案就是 n×(n+1)2−∑i=2nlcp(si−1,si)\frac{n \times (n+1)}{2}-\sum_{i=2}^n lcp(s_{i-1},s_i)2n×(n+1)i=2nlcp(si1,si)

对于求 lcplcplcp ,可以二分 hashhashhash ,对于排序,可以在 cmpcmpcmp 中调用求 lcplcplcp 的函数,然后只需要比较下一位或串长即可

#include <bits/stdc++.h>
#define K 793999
#define I inline
#define U unsigned long long
using namespace std;
const int N=1e5+5;
int n,p[6],D[150],S[5],a[N],t=-1;
char s[N],F[5];U b[N];long long ans;
struct O{
	char f[5];int d[150];U h[N];
	I U H(int l,int r){
		return h[r]-h[l-1]*b[r-l+1];
	}
}g[24];
struct Q{int h,ty;}q[N];
I bool newap(int x,int y){return S[x]<S[y];}
I int get(){
	for (int i=1;i<=4;i++) p[i]=i;
	sort(p+1,p+5,newap);
	for (int i=0;i<24;i++){
		bool J=1;
		for (int j=1;j<=4;j++)
			J&=(F[p[j]]==g[i].f[j]);
		if (J) return i;
	}
}
I int lcp(int x,int y){
	int l=0,h1=q[x].h,h2=q[y].h,r=min(n-h1+1,n-h2+1),t1=q[x].ty,t2=q[y].ty;
	while(l<r){
		int mid=(l+r+1)>>1;
		if (g[t1].H(h1,h1+mid-1)==g[t2].H(h2,h2+mid-1))
			l=mid;
		else r=mid-1;
	}
	return l;
}
I bool cmp(int x,int y){
	int l=lcp(x,y);
	if (l==min(n-q[x].h+1,n-q[y].h+1))
		return q[x].h>q[y].h;
	int u=g[q[x].ty].d[(int)s[q[x].h+l]];
	int v=g[q[y].ty].d[(int)s[q[y].h+l]];
	return u<v;
}
int main(){
	scanf("%s",s+1);n=strlen(s+1);ans=1ll*n*(n+1)/2;
	b[0]=1;for (int i=1;i<=4;i++) p[i]=i;
	S[D[(int)(F[1]='A')]=1]=n+1;
	S[D[(int)(F[2]='C')]=2]=n+1;
	S[D[(int)(F[3]='G')]=3]=n+1;
	S[D[(int)(F[4]='T')]=4]=n+1;
	for (int i=1;i<=n;i++) b[i]=b[i-1]*K;
	do{
		t++;for (int i=1;i<=4;i++)
			g[t].f[i]=F[p[i]],g[t].d[(int)F[p[i]]]=i;
		for (int i=1;i<=n;i++)
			g[t].h[i]=g[t].h[i-1]*K+g[t].d[(int)s[i]];
	}while(next_permutation(p+1,p+5));
	for (int i=n;i;i--)
		S[D[(int)s[i]]]=i,q[i]=(Q){i,get()},a[i]=i;
	sort(a+1,a+n+1,cmp);
	for (int i=2;i<=n;i++) ans-=lcp(a[i-1],a[i]);
	return printf("%lld\n",ans),0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值