不同字串个数
题目背景
因为NOI被虐傻了,蒟蒻的YJQ准备来学习一下字符串,于是它碰到了这样一道题:
题目描述
给你一个长为N的字符串,求不同的子串的个数
我们定义两个子串不同,当且仅当有这两个子串长度不一样 或者长度一样且有任意一位不一样。
子串的定义:原字符串中连续的一段字符组成的字符串
输入输出格式
输入格式:
第一行一个整数N
接下来一行N个字符表示给出的字符串
输出格式:
一行一个整数,表示不一样的子串个数
输入输出样例
说明
请使用64位整数来进行输出
(具体来说,C++和C选手请使用long long 类型,pascal选手请使用Int64)
由于输入文件过大,请使用 高效的读入方法(具体的,c++和c选手请不要使用cin,pascal选手不需要管)
对于30%的数据, $Nle 1000$
对于100%的数据, $Nle 10^5$
分析:
后缀数组入门好题,刚学,特地来水一波。
首先做这道题需要会后缀数组,并深入理解$height[]$数组的含义,然后需要知道一个非常重要的性质。下面这一段话引用自罗穗骞的论文:
每个子串一定是某个后缀的前缀,那么原问题等价于求所有后缀之间的不相同的前缀的个数。如果所有的后缀按照 suffix(sa[1]), suffix(sa[2]),suffix(sa[3]), …… ,suffix(sa[n])的顺序计算,不难发现,对于每一次新加进来的后缀 suffix(sa[k]),它将产生 n-sa[k]+1 个新的前缀。但是其中有height[k]个是和前面的字符串的前缀是相同的。所以 suffix(sa[k])将“贡献”出 n-sa[k]+1- height[k]个不同的子串。累加后便是原问题的答案。这个做法的时间复杂度为 O(n)。
知道这些这道题就可以轻松A过了。
Code:
//It is made by HolseLee on 13th Aug 2018 //Luogu.org P2408 #include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e5+7; int n,m,height[N],sa[N],rk[N],y[N],c[N]; ll ans; char s[N]; void Sort() { for(int i=0;i<=m;++i)c[i]=0; for(int i=1;i<=n;++i)c[rk[i]]++; for(int i=1;i<=m;++i)c[i]+=c[i-1]; for(int i=n;i>=1;--i)sa[c[rk[y[i]]]--]=y[i]; } void getsa() { for(int i=1;i<=n;++i)rk[i]=s[i]-'0',y[i]=i; Sort(); for(int k=1,cnt=0;cnt<n;m=cnt,k<<=1){ cnt=0; for(int i=1;i<=k;++i)y[++cnt]=n-k+i; for(int i=1;i<=n;++i)if(sa[i]>k)y[++cnt]=sa[i]-k; Sort(); swap(y,rk); rk[sa[1]]=cnt=1; for(int i=2;i<=n;++i) rk[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?cnt:++cnt; } } void getheight() { int k=0,j; for(int i=1;i<=n;++i){ if(k)k--; j=sa[rk[i]-1]; while(s[i+k]==s[j+k])k++; height[i]=k; } } int main() { scanf("%d%s",&n,s+1); m=75; getsa();getheight(); for(int i=1;i<=n;++i) ans+=(n-sa[i]+1-height[i]); printf("%lld",ans); return 0; }