Luogu P2408 不同子串个数|后缀数组
题意:给定一个字符串,求不同子串的个数。
一道很好的后缀数组题。
考虑子串一定是一个后缀的前缀,所以我们可以求出用后缀数组求出$LCP$(最长公共前缀),再求出每个后缀对答案的贡献。
即,长度减去出现过的前缀(一定是与排名前一个的后缀的$LCP$,因为对于一个后缀$i$,与其最相似的是排名与其相邻的后缀)。因此,以第$i$个字符(从0开始数)开头的后缀的贡献值为$n-i-height_{rank_i}$。
上代码
//这里字符串是从0开始的 #include<bits/stdc++.h> using namespace std; char st[100100];int rank[100100],nrank[100100],sa[100100],p[100100],height[100100],sum[100100],n; long long ans; bool same(int x,int y,int l) { if (rank[x]!=rank[y]) return false; if ((x+l>=n&&y+l<n) || (x+l<n&&y+l>=n)) return false; if (x+l>=n&&y+l>=n) return true; return rank[x+l]==rank[y+l]; }//判重 void calc_height() { //根据性质,计算height数组(当前字符串与排名是其上一个的字符串的LCP) int j=0; for (int i=0;i<n;i++) { if (j) j--; if (!rank[i]) { ans+=n-sa[rank[i]];j=0;//与前面没有LCP,即整串都可以作为答案 continue; } for (int k=sa[rank[i]]+j,l=sa[rank[i]-1]+j;;) { if (st[k]==st[l]) j++,k++,l++;else break; } height[rank[i]]=j; ans+=n-sa[rank[i]]-j;//计算答案,ans要用long long,否则会挂。 } } int main() { scanf("%d",&n); scanf("%s",&st); for (int i=0;i<n;i++) sum[rank[i]=int(st[i])]++; for (int i=1;i<=128;i++) sum[i]+=sum[i-1]; for (int i=n-1;i>=0;i--) sa[--sum[int(st[i])]]=i; int maxg=max(128,n); for (int l=1;l<n;l<<=1) { int k=-1; for (int i=n-l;i<n;i++) p[++k]=i; for (int i=0;i<n;i++) if (sa[i]-l>=0) p[++k]=sa[i]-l; for (int i=0;i<=maxg;i++) sum[i]=0; for (int i=0;i<n;i++) sum[rank[i]]++; for (int i=1;i<=maxg;i++) sum[i]+=sum[i-1]; for (int i=n-1;i>=0;i--) sa[--sum[rank[p[i]]]]=p[i];//基排求新排名 nrank[sa[0]]=0;int ns=0; for (int i=1;i<n;i++) { if (same(sa[i],sa[i-1],l)) nrank[sa[i]]=ns;else nrank[sa[i]]=++ns; } for (int i=0;i<n;i++) { rank[i]=nrank[i]; nrank[i]=0; } if (ns==n-1) break; } calc_height(); cout<<ans<<endl; return 0; }