link
也算是一道模板题了。
上一道题并没有提到的是,后缀数组还有一个很重要的应用,即\(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; }