题目链接:点击这里
题意:求出一个串中所有不同的含有字母X的子串。
后缀数组的经典运用就有求出一个串的所有子串,方法是从小到大考虑所有的 sai ( sai 表示字典序第i小的后缀的下标),因为 height 数组的含义是 sai 和 sai−1 两个后缀的相同前缀长度,而 sai 后缀有 n−sai 个前缀,减去重复的 heighti 个前缀就是不重复的子串,然后 ∑ 一下。 在这里就是要考虑含有X这个限制条件。 sai 后缀不再是有 n−sai 个前缀,而是要找到它之后的第一个X字母才能确定前缀个数,然后结合 height 数组瞎搞搞就好了。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <algorithm>
#include <map>
#include <set>
#define maxn 111111
using namespace std;
//以下为倍增算法求后缀数组
int wa[maxn],wb[maxn],wv[maxn],Ws[maxn];
int cmp(int *r,int a,int b,int l)
{return r[a]==r[b]&&r[a+l]==r[b+l];}
void da(const char *r,int *sa,int n,int m){
int i,j,p,*x=wa,*y=wb,*t;
for(i=0;i<m;i++) Ws[i]=0;
for(i=0;i<n;i++) Ws[x[i]=r[i]]++;
for(i=1;i<m;i++) Ws[i]+=Ws[i-1];
for(i=n-1;i>=0;i--) sa[--Ws[x[i]]]=i;
for(j=1,p=1;p<n;j*=2,m=p){
for(p=0,i=n-j;i<n;i++) y[p++]=i;
for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
for(i=0;i<n;i++) wv[i]=x[y[i]];
for(i=0;i<m;i++) Ws[i]=0;
for(i=0;i<n;i++) Ws[wv[i]]++;
for(i=1;i<m;i++) Ws[i]+=Ws[i-1];
for(i=n-1;i>=0;i--) sa[--Ws[wv[i]]]=y[i];
for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
return;
}
int sa[maxn],Rank[maxn],height[maxn];
//求height数组
void calheight(const char *r,int *sa,int n){
int i,j,k=0;
for(i=1;i<=n;i++) Rank[sa[i]]=i;
for(i=0;i<n;height[Rank[i++]]=k)
for(k?k--:0,j=sa[Rank[i]-1];r[i+k]==r[j+k];k++);
return;
}
char str[maxn];
int pos[maxn];
int cnt;
char ch[2];
long long slove(int n){
long long sum=0;
for (int i = 1; i <= n; i++) {
int id = lower_bound (pos, pos+cnt, sa[i])-pos;
id = (id >= cnt ? n : pos[id]);
int len = id-sa[i];
if (height[i] >= len)
sum += n-id-(height[i]-len);
else
sum += n-id;
}
return sum;
}
int main(){
int t, kase = 0;
scanf("%d",&t);
while(t--){
scanf("%s%s", ch, str);
da(str,sa,strlen(str)+1,130);
cnt = 0;
calheight(str,sa,strlen(str));
int n = strlen (str);
for (int i = 0; i < n; i++) if (str[i] == ch[0]) {
pos[cnt++] = i;
}
printf("Case #%d: %lld\n", ++kase, slove(n));
}
return 0;
}