题目
题意
给一个字符串,问该字符串中出现次数正好为 k 次的不同子串个数。
题解
后缀数组+lcp+rmq。先用后缀数组求出高度数组 lcp。
然后遍历 lcp,加上出现 k 次的子串个数:
ans+=max(lcp[i]...lcp[i+k−2])
a
n
s
+
=
m
a
x
(
l
c
p
[
i
]
.
.
.
l
c
p
[
i
+
k
−
2
]
)
还要减去子串出现大于k次的:
ans−=max(lcp[i−1]...lcp[i+k−2])
a
n
s
−
=
m
a
x
(
l
c
p
[
i
−
1
]
.
.
.
l
c
p
[
i
+
k
−
2
]
)
ans−=max(lcp[i]...lcp[i+k−1])
a
n
s
−
=
m
a
x
(
l
c
p
[
i
]
.
.
.
l
c
p
[
i
+
k
−
1
]
)
但是这样明显减多了一部分,最后 加上多减去的,
ans+=max(lcp[i−1]...lcp[i+k−1])
a
n
s
+
=
m
a
x
(
l
c
p
[
i
−
1
]
.
.
.
l
c
p
[
i
+
k
−
1
]
)
一定要注意 边界 和 k = 1 时的特判。
代码
#include <algorithm>
#include <bitset>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <iostream>
#include <list>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
using namespace std;
const int MAX = 100005;
int Rank[MAX];
int temp[MAX];
// 比较(Rank[i], Rank[i + k] ) 和 (Rank[j], Rank[j + k])
int len, k;
bool compare_sa(int i,int j) {
if(Rank[i] != Rank[j])
return Rank[i] < Rank[j];
else{
int ri = i + k <= len ? Rank[i + k] : -1;
int rj = j + k <= len ? Rank[j + k] : -1;
return ri < rj;
}
}
// 计算字符串 s的后缀数组
void construct_sa(string s, int *sa){
len = s.size();
//初始长度为1,Rank 直接取字符的编码
for (int i=0;i <= len; i++){
sa[i] = i;
Rank[i] = i < len ? s[i] : -1;
}
// 利用对长度为 k的排序的结果对长度为2k 的排序
for(k=1;k<=len;k *= 2){
sort(sa, sa + len + 1,compare_sa);
// 现在 temp中临时存储新计算的 Rank,再转存回 Rank 中
temp[sa[0]] = 0;
for(int i=1; i<=len; ++i) {
temp[sa[i]] = temp[sa[i - 1]] + (compare_sa(sa[i - 1], sa[i]) ? 1 : 0);
}
for(int i=0;i<=len;++i) {
Rank[i] = temp[i];
}
}
}
//计算 LCP。 !!每次计算 LCP 时,都需要清空 lcp[]
void construct_lcp(string s, int *sa, int *lcp) {
memset(Rank,0,sizeof(MAX));
int len1 = s.size();
for(int i=0;i<=len;i++)
Rank[sa[i]] = i;
int h = 0;
lcp[0] = 0;
for(int i=0;i<len1;i++){
//计算字符串中从位置i开始的后缀及其在后缀数组中的前一个后缀的LCP
int j = sa[Rank[i] - 1];
//将 h 先减去周字母的1长度,在保持前缀相同前提下不断增加
if(h > 0)
h--;
for(; j + h < len1 && i + h < len1; h++) {
if(s[j + h] != s[i + h])
break;
}
lcp[Rank[i] - 1] = h;
}
}
int dp[MAX][20];
void RMQ_init(int len,int *v)
{
memset(dp,0,sizeof(dp));
for(int i=1; i<=len; i++) dp[i][0]=v[i];
for(int j=1; (1<<j)<=len; j++)
for(int i=1;i+(1<<j)-1<=len;i++)
dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
int RMQ(int L,int R)
{
int k=0;
while((1<<(k+1))<=R-L+1) k++;
return min(dp[L][k],dp[R-(1<<k)+1][k]);
}
int sa[MAX];
int lcp[MAX];
int main(){
int t;
cin >> t;
string s;
while(t--){
int num;
cin >> num;
cin >> s;
int len = s.size();
memset(lcp, 0, sizeof(lcp));
construct_sa(s,sa);
construct_lcp(s,sa,lcp);
RMQ_init(len,lcp);
long long ans = 0;
for(int i=1;i<=len;++i){
if(num == 1)
ans += (len - sa[i]);
else
ans += RMQ(i, i+num-2);
if(i != len)
ans -= RMQ(i, i+num-1);
if(i != 1)
ans -= RMQ(i-1, i + num -2);
if(i > 1 && i < len)
ans += RMQ(i-1, i+num-1);
}
cout << ans << endl;
}
return 0;
}