Description
给定一个由 a b c
构成的字符串
S
S
S,可以选择任意两个相邻字符,用第一个覆盖第二个或用第二个覆盖第一个。求能变出多少不同的字符串,a b c
的个数两两差
≤
1
\leq 1
≤1,对
51123987
51123987
51123987 取模。
1 ≤ ∣ S ∣ ≤ 150 1 \leq |S| \leq 150 1≤∣S∣≤150。
Solution
序列自动机 + dp。令变出的字符串为
T
T
T,令它的压缩串为
T
′
T'
T′,如 aabcccbbaa
的压缩串为 abcba
,相当于去重。那么操作后得到的
T
′
T'
T′ 一定
S
S
S 的子序列,因为想要将一个字母向前向后挪,就要覆盖前面后面的字母,所以不会颠倒
S
S
S 中两个字母的相对顺序,只能保持或让其中一个字母覆盖。
所以
T
′
T'
T′ 中的字母是可以与
S
S
S 匹配的。令
f
i
,
a
,
b
,
c
f_{i,a,b,c}
fi,a,b,c 为,
T
′
T'
T′ 正与
S
i
S_i
Si 匹配,此时
T
T
T 中 a b c
出现的次数为
a
,
b
,
c
a ,\ b ,\ c
a, b, c 时,有多少个不同的
T
T
T。Tip:
f
f
f 的定义不是到了
S
i
S_i
Si,a b c
出现的次数,而是匹配到
S
i
S_i
Si。比如样例 4 abca
。
f
1
,
2
,
0
,
0
=
1
f_{1,2,0,0} = 1
f1,2,0,0=1 是对的,因为
T
T
T 中的两个 a
都是
S
1
S_1
S1 变出来的。
由于 n ≤ 150 n \leq 150 n≤150,所以可以四维枚举 i , a , b , c i ,\ a ,\ b ,\ c i, a, b, c。如果有 a + b + c = n ∧ a + b + c = n \land a+b+c=n∧ ∣ a − b ∣ , ∣ a − c ∣ , ∣ b − c ∣ ≤ 1 |a-b|, \ |a - c|, \ |b-c| \leq1 ∣a−b∣, ∣a−c∣, ∣b−c∣≤1,那么将 f i , a , b , c f_{i,a,b,c} fi,a,b,c 计入答案。那么如何转移?
S
i
S_i
Si 为「要去覆盖别的字母」的字母时。那么从它开始覆盖即可,如
S
i
=
S_i =
Si= a
,
f
i
,
a
+
1
,
b
,
c
=
f
i
,
a
+
1
,
b
,
c
+
f
i
,
a
,
b
,
c
f_{i,a+1,b,c} = f_{i,a+1,b,c} + f_{i,a,b,c}
fi,a+1,b,c=fi,a+1,b,c+fi,a,b,c。因为在枚举
a
,
b
,
c
a, \ b, \ c
a, b, c,所以会转移到
f
i
,
a
+
j
,
b
,
c
f_{i, a + j, b, c}
fi,a+j,b,c。
S
i
S_i
Si 为「要被覆盖的字母」的字母时,将它转移到「要去覆盖别的字母」的字母即可。
那么如何确定一个 「要去覆盖别的字母」呢,如果枚举它的下标来确定,可能会重复计入答案。比如
S
S
S = caba
,其中一个
T
T
T 为 aaaa
即为为
∑
i
=
1
∣
S
∣
f
i
,
4
,
0
,
0
=
1
\sum_{i=1}^{|S|} f_{i, 4,0,0} = 1
∑i=1∣S∣fi,4,0,0=1,如果枚举下标
∑
i
=
1
∣
S
∣
f
i
,
4
,
0
,
0
=
1
\sum_{i=1}^{|S|} f_{i, 4,0,0} = 1
∑i=1∣S∣fi,4,0,0=1 会为
2
2
2,分别在
i
=
2
,
4
i=2, \ 4
i=2, 4。可以发现
i
=
2
i = 2
i=2 的 a
在覆盖了 c
和 ba
,
i
=
4
i = 4
i=4 的 a
就没有必要覆盖 cab
了。
所以可以预处理一个 n x t i , j nxt_{i,j} nxti,j 为 n x t i , j ≥ i ∧ S n x t i , j = j nxt_{i,j} \ge i \land S_{nxt_{i,j}} = j nxti,j≥i∧Snxti,j=j。也就是 i i i 后第一个字母 j j j 出现的下标,包括 i i i。转移方程如下
f 1 , 0 , 0 , 0 = 1 f_{1,0,0,0} = 1 f1,0,0,0=1
f n x t i , 0 , a + 1 , b , c = f n x t i , 0 , a + 1 , b , c + f i , a , b , c f_{nxt_{i,0}, a + 1, b, c} = f_{nxt_{i,0}, a + 1, b, c} + f_{i,a,b,c} fnxti,0,a+1,b,c=fnxti,0,a+1,b,c+fi,a,b,c
f n x t i , 1 , a , b + 1 , c = f n x t i , 1 , a , b + 1 , c + f i , a , b , c f_{nxt_{i,1}, a, b + 1, c} = f_{nxt_{i,1}, a, b + 1, c} + f_{i,a,b,c} fnxti,1,a,b+1,c=fnxti,1,a,b+1,c+fi,a,b,c
f n x t i , 2 , a , b , c + 1 = f n x t i , 2 , a , b , c + 1 + f i , a , b , c f_{nxt_{i,2}, a, b, c + 1} = f_{nxt_{i,2}, a, b, c + 1} + f_{i,a,b,c} fnxti,2,a,b,c+1=fnxti,2,a,b,c+1+fi,a,b,c
因为每个字符出现的次数不会超过 n + 2 3 \frac{n + 2}{3} 3n+2,所以时空复杂度为 O ( n 4 27 ) O(\frac{n^4}{27}) O(27n4)。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 150 + 5, p = 51123987;
char s[N];
int f[N][(N + 2) / 3][(N + 2) / 3][(N + 2) / 3], nxt[N][3], n, ans;
int main() {
scanf("%d%s", &n, s + 1);
nxt[n + 1][0] = nxt[n + 1][1] = nxt[n + 1][2] = n + 1;
for(int i = n; i >= 1; i--) {
nxt[i][0] = nxt[i + 1][0];
nxt[i][1] = nxt[i + 1][1];
nxt[i][2] = nxt[i + 1][2];
if(s[i] == 'a') nxt[i][0] = i;
if(s[i] == 'b') nxt[i][1] = i;
if(s[i] == 'c') nxt[i][2] = i;
}
f[1][0][0][0] = 1;
for(int i = 1; i <= n; i++)
for(int a = 0; a <= (n + 2) / 3; a++)
for(int b = 0; b <= (n + 2) / 3; b++)
for(int c = 0; c <= (n + 2) / 3; c++)
{
if(a + b + c == n && abs(a - b) <= 1 && abs(a - c) <= 1 && abs(b - c) <= 1)
ans = (ans + f[i][a][b][c]) % p;
f[nxt[i][0]][a + 1][b][c] = (f[nxt[i][0]][a + 1][b][c] + f[i][a][b][c]) % p;
f[nxt[i][1]][a][b + 1][c] = (f[nxt[i][1]][a][b + 1][c] + f[i][a][b][c]) % p;
f[nxt[i][2]][a][b][c + 1] = (f[nxt[i][2]][a][b][c + 1] + f[i][a][b][c]) % p;
}
printf("%d\n", ans);
return 0;
}