不同子串个数
也算是一道模板题了。
上一道题并没有提到的是,后缀数组还有一个很重要的应用,即\(height\)数组,以下简称h。\(h_i\)的定义是排名为i的后缀与排名为i-1的后缀的最长公共前缀长度,而h数组我们可以\(O(N)\)求得。方法如下。
首先有一个结论,\(h[rank[i-1]]-1\le h[rank[i]]\),证明没有看懂,以后看懂了再来填坑。代码如下,先死记硬背吧。
for(int i=1,j;i<=m;h[a[i++]]=hh)
for(hh?hh--:0,j=pl[a[i]-1];w[i+hh]==w[j+hh];hh++);
还留有一个问题,假如我们已经求出了h数组,那和本题要求的东西有什么关系吗。有,首先字串数量是很好求的,是\(len\times(len+1)/2\)。问题在于其中有多少个字串有重复。可以想到把这些重复子串按照左端点位置进行归类,对于同一类的重复子串,会发现存在一些后缀的某些前缀是相同的,这些一样的前缀即是重复子串,毕竟每一个后缀的前缀集合的并集即是原串的子串集合。而很明显这些前缀相同的后缀会在排序之后分到一起,它们的数量就是h数组所保存的。
代码。
#include<cstdio>
#include<cstring>
//#define zczc
#define ll long long
using namespace std;
const int N=100010;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
wh*=f;return;
}
inline int get(){
char w=getchar();
while(w<'a'||w>'z')w=getchar();
return w-'a'+2;
}
inline int max(int s1,int s2){
return s1<s2?s2:s1;
}
int m,w[N],a[N];
struct node{
int id,x,y;
}s[N],r[N];
int maxn,num[N],sum[N];
inline void init(){
memset(num,0,sizeof(int)*(maxn+2));
memset(sum,0,sizeof(int)*(maxn+2));
maxn=0;return;
}
void solve(){
for(int i=1;i<=m;i++)maxn=max(s[i].y,maxn),num[s[i].y]++;
for(int i=1;i<=maxn;i++)sum[i]=sum[i-1]+num[i];
for(int i=1;i<=m;i++)r[sum[s[i].y-1]+(num[s[i].y]--)]=s[i];init();
for(int i=1;i<=m;i++)maxn=max(maxn,r[i].x),num[r[i].x]++;
for(int i=1;i<=maxn;i++)sum[i]=sum[i-1]+num[i];
for(int i=1;i<=m;i++)s[sum[r[i].x]-(--num[r[i].x])]=r[i];
}
int pl[N],h[N],hh;
void workh(){
for(int i=1,j;i<=m;h[a[i++]]=hh)
for(hh?hh--:0,j=pl[a[i]-1];w[i+hh]==w[j+hh];hh++);
return;
}
signed main(){
#ifdef zczc
freopen("in.txt","r",stdin);
#endif
read(m);for(int i=1;i<=m;i++)w[i]=a[i]=get();
for(int n=1;n<=m;n<<=1){
for(int i=1;i<=m;i++)s[i].id=i,s[i].x=a[i],s[i].y=i+n>m?1:a[i+n];solve();maxn=0;
for(int i=1;i<=m;i++)a[s[i].id]=(i==1||s[i].x!=s[i-1].x||s[i].y!=s[i-1].y)?++maxn:maxn;
}
workh();ll ans=(ll)m*(m-1)>>1;
for(int i=1;i<=m;i++)ans-=h[i];
printf("%lld",ans+m);
return 0;
}
一如既往,万事胜意