P2408 不同子串个数

链接

P2408


题意

给你一个长为 \(n\) 的字符串 \(S\),求本质不同的子串的个数。


分析

这是一个经典字符串问题。我们难以用 kmp 或 ac 自动机来做,所以只能考虑把 \(S\) 的 SA 跑出来。
这样任意一个子串都是 \(S\) 某一个后缀的一个前缀,由于有 \(ht\) 这样有用的东西存在,所以我们就直接看排序后的后缀。

image

我们发现 \(ht\) 数组是当前后缀与上一后缀的 LCP,也正是相同前缀数。比如 \(aa\)\(aaa\)\(ht\)\(2\) 也就是 \(a\)\(aa\) 这两个相同前缀。又因为后缀的前缀是原串的子串,所以这里的 \(ht\) 正是重复的子串数,这启示我们用总子串数减去重复子串数来得到答案。
但这里的 \(ht\) 只是当前后缀和前一后缀的重复数,如果还有其他一样重复的呢?我们发现由于后缀数组按字典序排序的良好性质,重复的子串在后缀数组中一定是在一段连续的区间(比如上面的重复子串 \(a\) 就出现在了 \([1,6]\) 的后缀中)。所以 \(ht\) 会将这些所有的重复子串都囊括完。最后我们发现重复子串数就是 \(\sum\limits_{i=1}^n ht_i\),于是我们的答案就是 \(\frac{n\times(n+1)}2-\sum\limits_{i=1}^n ht_i\)


代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
inline int read(){
	int p=0,f=1;
	char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){p=p*10+c-'0';c=getchar();}
	return p*f;
}
const int N=1e5+5;
int n,m,rk[N<<1],sa[N],ht[N];
struct llmmkk{
	int fi,se,ref;
}st[N<<1],tmp[N<<1];
int cnt[N];
inline void jsort(){
	for(int i=0;i<=n;i++)cnt[i]=0;
	for(int i=1;i<=n;i++)cnt[st[i].se]++;
	for(int i=1;i<=n;i++)cnt[i]+=cnt[i-1];
	for(int i=n;i>0;i--)tmp[cnt[st[i].se]]=st[i],cnt[st[i].se]--;	
	for(int i=0;i<=n;i++)cnt[i]=0;
	for(int i=1;i<=n;i++)cnt[tmp[i].fi]++;
	for(int i=1;i<=n;i++)cnt[i]+=cnt[i-1];
	for(int i=n;i>0;i--)st[cnt[tmp[i].fi]]=tmp[i],cnt[tmp[i].fi]--;
}
int ston[257];
inline void SA(string S){
	n=S.length();
	for(int i=0;i<n;i++)ston[signed(S[i])]+=(!ston[signed(S[i])]);
	for(int i=1;i<=256;i++)ston[i]+=ston[i-1];	
	for(int i=1;i<=n;i++)rk[i]=ston[signed(S[i-1])];
	for(int k=1,t;k<=(n<<1);k<<=1){
		for(int i=1;i<=n;i++)
			st[i].fi=rk[i],st[i].se=rk[i+k],st[i].ref=i;	
		jsort();t=0;
		for(int i=1;i<=n;i++){
			if((st[i].fi^st[i-1].fi)||(st[i].se^st[i-1].se))t++;
			rk[st[i].ref]=t;			
		}if(t==n)break;
	}
	for(int i=1;i<=n;i++)sa[rk[i]]=i;
	for(int i=1;i<=n;i++){
		ht[rk[i]]=max(ht[rk[i-1]]-1,0ll);
		while(S[sa[rk[i]-1]+ht[rk[i]]-1]==S[i+ht[rk[i]]-1])
			ht[rk[i]]++;
	}
}
string S;
int ans,T;
signed main(){
	cin>>n>>S;SA(S);ans=(n+1)*n/2;
	for(int i=1;i<=n;i++)ans-=ht[i];
	cout<<ans<<'\n';
	return 0;
}
posted @ 2022-01-24 11:27  llmmkk  阅读(68)  评论(0编辑  收藏  举报