Description
给定一个由 a b c
构成的字符串
,可以选择任意两个相邻字符,有第一个覆盖第二个或用第二个覆盖第一个。求能变出多少不同的字符串,a b c
的个数两两差
,对
取模。
。
Solution
序列自动机 + dp。令变出的字符串为
,令它的压缩串为
,如 aabcccbbaa
的压缩串为 abcba
,相当于去重。那么操作后得到的
一定
的子序列,因为想要将一个字母向前向后挪,就要覆盖前面后面的字母,所以不会颠倒
中两个字母的相对顺序,只能保持或让其中一个字母覆盖。
所以
中的字母是可以与
匹配的。令
为,
正与
匹配,此时
中 a b c
出现的次数为
时,有多少个不同的
。Tip:
的定义不是到了
,a b c
出现的次数,而是匹配到
。比如样例 4 abca
。
是对的,因为
中的两个 a
都是
变出来的。
由于 ,所以可以四维枚举 。如果有 ,那么将 计入答案。那么如何转移?
为「要去覆盖别的字母」的字母时。那么从它开始覆盖即可,如 $S_i = $ a
,
。因为在枚举
,所以会转移到
。
为「要被覆盖的字母」的字母时,将它转移到「要去覆盖别的字母」的字母即可。
那么如何确定一个 「要去覆盖别的字母」呢,如果枚举它的下标来确定,可能会重复计入答案。比如
= caba
,其中一个
为 aaaa
即为为
,如果枚举下标
会为
,分别在
。可以发现
的 a
在覆盖了 c
和 ba
,
的 a
就没有必要覆盖 cab
了。
所以可以预处理一个 为 。也就是 后第一个字母 出现的下标,包括 。转移方程如下
因为每个字符出现的次数不会超过 ,所以时空复杂度为 。
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;
}