2021杭电多校第五场
Another String
题意
给定一个长度为n
的字符串S
,和一个正整数k
。
如果两个长度的相同的字符串(a,b)满足
a
[
i
]
≠
b
[
i
]
a[i] \neq b[i]
a[i]=b[i],
i
i
i的个数不超过k
,则称两个字符串满足k−matching。
对于一个分割线t
,将字符串S
分成两个子串
A
=
S
[
1
,
t
]
,
B
=
S
[
t
+
1
,
n
]
A=S[1, t],\ B=S[t+1,n]
A=S[1,t], B=S[t+1,n]。
求A
的子串和B
子串中有多少对满足k−matching。
求出分割线 t ∈ [ 1 , n − 1 ] t\in [1, n-1] t∈[1,n−1]的所有值。
思路
定义 F [ i ] [ j ] F[i][j] F[i][j]为 A A A以 i i i开头的子串 和 B B B以 j j j开头的子串 满足k−matching的个数(也等于最长的长度),
则对于每一个分割线t
,都要求出
∑
i
=
1
t
∑
j
=
t
+
1
n
m
i
n
(
F
[
i
]
[
j
]
,
t
−
i
+
1
)
\sum_{i=1}^{t}\sum_{j=t+1}^{n}min(F[i][j], t-i+1)
∑i=1t∑j=t+1nmin(F[i][j],t−i+1)。(
t
−
i
+
1
t-i+1
t−i+1是因为左边的子串不能超过分割线t
)
首先考虑如何求出 F [ i ] [ j ] F[i][j] F[i][j]:
-
暴力求解:对于每一个 ( i , j ) (i,j) (i,j)都求一遍 F [ i ] [ j ] F[i][j] F[i][j],复杂度为 O ( n 3 ) O(n^3) O(n3)。
暴力会导致重复比较,如下图:
- 通过枚举L和R之差
t
(R=L+t),然后用双指针来计算L和R的len(满足k-mat的最大长度)是多少,细节可以看代码中的注释,这样做的复杂度就是 O ( n 2 ) O(n^2) O(n2)。
再考虑如何计算出每一个t
对应的
∑
i
=
1
t
∑
j
=
t
+
1
n
m
i
n
(
F
[
i
]
[
j
]
,
t
−
i
+
1
)
\sum_{i=1}^{t}\sum_{j=t+1}^{n}min(F[i][j], t-i+1)
∑i=1t∑j=t+1nmin(F[i][j],t−i+1):
- 暴力的复杂度是 O ( n 3 ) O(n^3) O(n3)。
如何缩短时间的复杂度?
在 t ′ = 5 t'=5 t′=5时需要计算 ∑ i = 1 5 m i n ( F [ i ] [ 6 ] , t ′ − i + 1 ) \sum_{i=1}^{5}min(F[i][6],t'-i+1) ∑i=15min(F[i][6],t′−i+1),在 t = 4 t=4 t=4时需要计算 ∑ i = 1 4 m i n ( F [ i ] [ 5 ] , t − i + 1 ) + ∑ i = 1 4 m i n ( F [ i ] [ 6 ] , t − i + 1 ) \sum_{i=1}^{4}min(F[i][5],t-i+1)+\sum_{i=1}^{4}min(F[i][6],t-i+1) ∑i=14min(F[i][5],t−i+1)+∑i=14min(F[i][6],t−i+1)。
可以看出,如果暴力计算的话,会重复计算 ∑ i = 1 4 F [ i ] [ 6 ] \sum_{i=1}^{4}F[i][6] ∑i=14F[i][6]。
考虑如何将 t ′ = 5 t'=5 t′=5的状态推到 t = 4 t=4 t=4的状态?
因为t
的值不相同,所以无法直接记录
∑
m
i
n
(
F
[
i
]
[
j
]
,
t
′
−
i
+
1
)
\sum min(F[i][j],t'-i+1)
∑min(F[i][j],t′−i+1)的值。
可以将
F
[
i
]
[
j
]
F[i][j]
F[i][j]的值记录在数组
c
n
t
[
i
]
[
F
[
i
]
[
j
]
]
cnt[i][F[i][j]]
cnt[i][F[i][j]]中,又因为每次枚举t
都会向前移动一位(
t
′
−
t
=
1
t'-t=1
t′−t=1),
所以只需要考虑等于
(
t
′
−
i
+
1
)
=
(
t
−
i
+
1
+
1
)
(t'-i+1)=(t-i+1+1)
(t′−i+1)=(t−i+1+1)的值,这个值超过了分割线t
:
ans -= cnt[i][t - i + 1 + 1];
cnt[i][t - i + 1] += cnt[i][t - i + 1 + 1];
然后计算 ∑ i = 1 t m i n ( F [ i ] [ t ′ ] , t − i + 1 ) \sum_{i=1}^{t}min(F[i][t'],t-i+1) ∑i=1tmin(F[i][t′],t−i+1)的值。
int j = t + 1;// B的子串的起始位置
for(int i = 1; i <= t; i++){
int len = min(F[i][j], t - i + 1);// 不能超过分割线
cnt[i][len]++;
ans += len;
}
还需要去掉左边以 t ′ t' t′开头的子串的贡献。
ans -= cnt[t + 1][1] * 1;
AC的代码
#include<bits/stdc++.h>
using namespace std;
const int N = 3000+10;
int n, k, F[N][N], res[N], cnt[N][N];
char s[N];
void init(){
memset(cnt, 0, sizeof cnt);
memset(F, 0, sizeof F);
memset(res, 0, sizeof res);
}
int main(){
int T;
cin>>T;
while(T--){
init();// 初始化
cin>>n>>k;
cin>>s+1;
// =============================================================
// 预处理F[i][j]
for(int t = 1; t < n; t++){ // 枚举两个子串的左端点之差
int dif = 0;// 记录两个子串的不同
for(int L = 1, R = L + t, len = 0; R <= n; L++, R++){
// len表示对于此时的(i,j)满足k-match最长的长度
while(R + len <= n && dif <= k){
if(s[L + len] != s[R + len]){
dif++;
}
len++;
}
if(dif <= k){
F[L][R] = len;
}
else{ //多匹配了一位
F[L][R] = len - 1;
}
// 将i和j都向右移动一位
if(s[L] != s[R]) {
dif--;
}
len--;
}
}
// =============================================================
int ans = 0;
// 枚举A和B的分割位置
for(int t = n - 1; t >= 1; t--){
for(int i = 1; i <= t; i++) {
ans -= cnt[i][t - i + 1 + 1];
cnt[i][t - i + 1] += cnt[i][t - i + 1 + 1];
}
int j = t + 1;// B的子串的起始位置
for(int i = 1; i <= t; i++){
int len = min(F[i][j], t - i + 1);// 不能超过分割线
cnt[i][len]++;
ans += len;
}
ans -= cnt[t + 1][1] * 1;
res[t] = ans;
}
// =============================================================
for(int i = 1; i < n; i++) {
cout<<res[i]<<endl;
}
}
return 0;
}