题意:给定你一个字符串s,然后问你在s的所有子串中,有多少子串在s中恰好出现了k次?
思路:~~好像是后缀自动机模板题,但是我木有学过 ~~
还可以用后缀数组加上线段树区间查询的操作来解决。
我们这样想,假如我们对于字符串的每一个后缀i去找在排名上和它相差k-1个的后缀,然后求出这两个后缀的的最长公共前缀长度为L,也就是区间[i+1,i_k-1]的height数组最小值。那么我们就可以知道对于这长度为L的子串,是在s串中出现过至少k次的,但是我们这道题目要求的是出现次数恰好为k次的子串, 所以我们需要计算一下排名差为k+1的后缀的最长共前缀,但是这个差有两个,区间[i,i+k-1]和区间[i+1,i+k],但是减去这两个区间的时候会减重,所以还要再加上区间[i,i+k]这段区间的长度。一开始求出来的是>=k,然后是求出>=k+1减去即可,但是求两次相减多减了>=k+2的情况,再加回来。
再就是对于k等于1的情况,特判一下,这种情况下,区间的左右端点用上面的方法是错误的。这样想,对于一个位置在i处长度为L的后缀字符串,求出height[i]和height[i-1]的最大值,用长度减去即可,这段后缀贡献的答案即为相减所得。
比如suffix[sa[i]] = “abcad”,suffix[sa[i-1]] = “abcd”,suffix[sa[i+1]] =
“ab”,显然height[i] = 3,height[i+1] = 2,len(suffix[sa[i]]) =
5,那么对于sa[i]这个后缀的前缀对答案的贡献为2,即"abca"和"abcad"这两个子串的贡献
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
const int MAXN = 1e5 + 7;
char str[MAXN];
/***Suffix_Array****/
int ans,n,m,x[MAXN],y[MAXN],c[MAXN],sa[MAXN],rk[MAXN],height[MAXN];
//!!! m = 字符集大小
void SA_build(){
//初始化的计数排序
for(int i = 0;i < m;i ++) c[i] = 0;
for(int i = 0;i < n;i ++) c[x[i]=str[i]]++;
for(int i = 1;i < m;i ++) c[i] += c[i-1];
for(int i = n-1;i >= 0;i --) sa[--c[x[i]]] = i;
for(int k = 1;k <= n;k <<= 1){
int p = 0;
for(int i = n-k;i < n;i ++) y[p++] = i;
for(int i = 0;i < n;i ++)
if(sa[i] >= k) y[p++] = sa[i] - k;
for(int i = 0;i < m;i ++) c[i] = 0;
for(int i = 0;i < n;i ++) c[x[i]]++;
for(int i = 1;i < m;i ++) c[i] += c[i-1];
for(int i = n-1;i >= 0;i --) sa[--c[x[y[i]]]] = y[i];
swap(x,y);
p = 1;
x[sa[0]] = 0;
for(int i = 1;i < n;i ++)
x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&((sa[i-1]+k>=n?-1:y[sa[i-1]+k])==(sa[i]+k>=n?-1:y[sa[i]+k]))?p-1:p++;
if(p > n) break;
m = p;
}
}
/*******************/
void height_build(){
for(int i = 0;i < n;i ++) rk[sa[i]] = i;
int k = 0;height[0] = 0;
for(int i = 0;i < n;i ++){
if(!rk[i]) continue;
if(k) k--;
int j = sa[rk[i]-1];
while(i+k<n&&j+k<n&&str[i+k]==str[j+k]) k++;
height[rk[i]] = k;
}
}
int tree[MAXN<<2];
void pushup(int rt){
tree[rt] = min(tree[rt<<1],tree[rt<<1|1]);
}
void build(int rt,int l,int r){
if(l == r){
tree[rt] = height[l-1];
return ;
}
int mid = (l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
pushup(rt);
}
int query(int rt,int l,int r,int L,int R){
if(l >= L && r <= R) return tree[rt];
int mid = (l+r)>>1;
int ans = inf;
if(L <= mid) ans = min(ans,query(rt<<1,l,mid,L,R));
if(R > mid) ans = min(ans,query(rt<<1|1,mid+1,r,L,R));
return ans;
}
int main()
{
int t,k;
scanf("%d",&t);
while(t--){
m = 200;//这里m要初始化 因为在求sa数组的时候m变了
memset(height,0,sizeof(height));
scanf("%d",&k);
scanf("%s",str);
n = strlen(str);
SA_build();
height_build();
build(1,1,n);
// for(int i = 0;i < n;i ++) printf(i == n-1?"%d\n":"%d ",sa[i]);
// for(int i = 0;i < n;i ++) printf(i == n-1?"%d\n":"%d ",height[i]);
if(k > n-1) { puts("0");continue; }
int ans = 0;
if(k == 1){
for(int i = 0;i < n;i ++){
ans += max(0,n-sa[i]-max(height[i],height[i+k]));
}
}
else if(k > 1){
for(int i = 1;i + k - 1 <= n;i ++){
ans += query(1,1,n,i+1,i+k-1);
ans -= query(1,1,n,i,i+k-1);
if(i + k <= n){
ans -= query(1,1,n,i+1,i+k);
ans += query(1,1,n,i,i+k);
}
}
}
printf("%d\n",ans);
}
return 0;
}