P2408 不同子串个数
链接
题意
给你一个长为 \(n\) 的字符串 \(S\),求本质不同的子串的个数。
分析
这是一个经典字符串问题。我们难以用 kmp 或 ac 自动机来做,所以只能考虑把 \(S\) 的 SA 跑出来。
这样任意一个子串都是 \(S\) 某一个后缀的一个前缀,由于有 \(ht\) 这样有用的东西存在,所以我们就直接看排序后的后缀。
我们发现 \(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;
}