CF17C Balance(序列自动机 + 计数 dp)

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 1S150

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 Ta b c 出现的次数为 a ,   b ,   c a ,\ b ,\ c a, b, c 时,有多少个不同的 T T T。Tip: f f f 的定义不是到了 S i S_i Sia 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 n150,所以可以四维枚举 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 ab, ac, bc1,那么将 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 Taaaa 即为为 ∑ i = 1 ∣ S ∣ f i , 4 , 0 , 0 = 1 \sum_{i=1}^{|S|} f_{i, 4,0,0} = 1 i=1Sfi,4,0,0=1,如果枚举下标 ∑ i = 1 ∣ S ∣ f i , 4 , 0 , 0 = 1 \sum_{i=1}^{|S|} f_{i, 4,0,0} = 1 i=1Sfi,4,0,0=1 会为 2 2 2,分别在 i = 2 ,   4 i=2, \ 4 i=2, 4。可以发现 i = 2 i = 2 i=2a 在覆盖了 cba i = 4 i = 4 i=4a 就没有必要覆盖 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,jiSnxti,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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值