题意:求一个字符串包含某一个特定字符的互不相同的子串个数。
思考过程:
1.如果是求一个字符串所有互不相同的子串个数,这是容易的。因为只需要遍历这个字符串所有后缀,依次计算这个后缀对答案的贡献即可。
遍历所有后缀并且逐个计算贡献是后缀数组解决字符串计数问题的关键。
2.在模板题中,每个后缀的贡献是这个后缀的所有前缀数-lcp中的前缀数,这个问题中每个后缀的贡献是什么?类比后不难发现是后缀的所有前缀数-重复出现的前缀数-不包含特定字符的前缀数,当然有可能某一前缀即重复又不包含特定字符。
如何计算每一个后缀不包含特定字符的前缀数?只需要找到离这个后缀的开头最近的特定字符的位置即可。
3.注意一些细节,比如有可能某一前缀即重复又不包含特定字符,统计答案的时候应该是取二者的并集
#include<bits/stdc++.h> #define FAST ios::sync_with_stdio(false),cin.tie(0),cout.tie(0) #define INF 0x3f3f3f3f typedef long long ll; const int maxn = 1e5+5; using namespace std; int n,m; char s[maxn],ch; int height[maxn],dis[maxn]; int sa[maxn],rk1[maxn],tp1[maxn],tax[maxn]; int *rk=rk1, *tp=tp1; void bucket() { for (int i=0; i<=m; i++) tax[i]=0; for (int i=1; i<=n; i++) tax[rk[i]]++; for (int i=1; i<=m; i++) tax[i]+=tax[i-1]; for (int i=n; i>=1; i--) sa[tax[rk[tp[i]]]--]=tp[i]; } void get_sa() { m=128; for (int i=1; i<=n; i++) rk[i]=s[i],tp[i]=i; bucket(); for (int k=1,p=0; p<n; m=p,k<<=1) { p=0; for (int i=1; i<=k; i++) tp[++p]=n-k+i; for (int i=1; i<=n; i++) if (sa[i]>k) tp[++p]=sa[i]-k; bucket(); swap(tp,rk); rk[sa[1]]=p=1; for (int i=2; i<=n; i++) rk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]] && tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p; } } void get_height() { int k=0; for (int i=1; i<=n; i++) { if (k) k--; int j=sa[rk[i]-1]; while(s[i+k]==s[j+k]) k++; height[rk[i]]=k; } } int get_d(int cur) { for (int i=cur+1; i<=n; i++) if (s[i]==ch) return i-cur; return -1; } void get_dis() { dis[0]=get_d(0); if (dis[0]==-1) { for (int i=1; i<=n; i++) dis[i]=-1; return; } for (int i=1; i<=n; i++) { if (s[i]==ch) dis[i]=0; else if (dis[i-1]>=1) dis[i]=dis[i-1]-1; else dis[i]=get_d(i); } } int kase; int main() { FAST; cin>>kase; for (int t=1; t<=kase; t++) { cin>>ch; cin>>s+1; n=strlen(s+1); get_sa(); get_height(); get_dis(); ll ans=0; for (int i=1; i<=n; i++) { if (dis[i]<0) continue; ans+=n-i+1-max(height[rk[i]],dis[i]); } cout<<"Case #"<<t<<": "<<ans<<endl; } return 0; }
反思:
1.我wa了几次的原因是因为在遍历所有后缀的时候:
for (int i=1; i<=n; i++) { if (dis[i]<0) continue; ans+=n-sa[i]+1-max(height[rk[i]],dis[i]); }
这边for循环枚举是从左向右枚举后缀的左端点,而不是字典序,所以后缀的长度不是n-sa[i]+1!!!这反应出我对后缀数组中几个数组的意思没有熟练掌握。
sa[i]=k表示字典序为i的后缀的首字符在原字符串的第k位
rk[i]=k表示在原字符串中以第i位开头的后缀字典序为k
height[i]=k表示字典序为i的后缀与字典序为i-1的后缀的lcp长度