AtCoder3950 [AGC022E] Median Replace(DFA + dp)

problem

solution

可以从 DFA \text{DFA} DFA 的思想来考虑这道题。

考虑建一个 DFA \text{DFA} DFA 只接受最后可以变成字符串 1 1 1 的原串。

因为每次是选择三个 连续/相邻 的位置操作,限制是比较强的,无非有三种情况。

  • case1 三个全 1 1 1,操作后只剩 1 1 1 1 1 1,相当于删去两个 1 1 1
  • case2 三个全 0 0 0,操作后只剩 1 1 1 0 0 0,相当于删去两个 0 0 0
  • case3 剩下的组合,操作后只剩 1 1 1 0 / 1 0/1 0/1,但都相当于删去 01 01 01 各一个。

显然贪心地我们是不会操作 case1 减少 1 1 1 的数量的。而连续的 0 0 0 的数量也不会超过 2 2 2 个,超过一定可以操作回来。

只有 1 1 1 的数量大于等于 0 0 0 的数量就是合法的。

事实上最后情况 1 1 1 的数量绝对不可能跟 0 0 0 数量相同,因为题目保证输入串是奇数长度,而每次的操作都减少两个长度。

1 1 1 多了反正都会被接受,没必要在 DFA \text{DFA} DFA 上建立这么多状态。

我们将所有 1 1 1 的个数超过两个的状态都连回个数为 2 2 2 的状态,然后接受这个状态即可。

所以 DFA \text{DFA} DFA 的状态数是 1 ( 0 / 1 / 2 ) − 0 ( 0 / 1 / 2 ) = 3 ⋅ 3 = 9 1(0/1/2)-0(0/1/2)=3·3=9 1(0/1/2)0(0/1/2)=33=9 种的。

最后就是在这个 DFA \text{DFA} DFA 上跑 d p dp dp 即可。

f ( i , j , k ) : f(i,j,k): f(i,j,k): 到字符串的第 i i i 位,此时状态为 j j j 1 1 1 k k k 0 0 0 的方案数。考虑向 i + 1 i+1 i+1 位转移。

  • s i + 1 = 0 s_{i+1}=0 si+1=0,直接增加 0 0 0 个个数即可。
    • k = 2 k=2 k=2case2 操作。 f ( i , j , 2 ) → f ( i + 1 , j , 0 ) f(i,j,2)\rightarrow f(i+1,j,0) f(i,j,2)f(i+1,j,0)
    • k ≠ 2 k\neq 2 k=2 f ( i , j , k ) → f ( i + 1 , j , k + 1 ) f(i,j,k)\rightarrow f(i+1,j,k+1) f(i,j,k)f(i+1,j,k+1)
  • s i + 1 = 1 s_{i+1}=1 si+1=1。在这个时候才考虑跟 0 0 0 的抵消情况。
    • k = 0 k=0 k=0 f ( i , j , 0 ) → f ( i + 1 , min ⁡ ( j + 1 , 2 ) , 0 ) f(i,j,0)\rightarrow f(i+1,\min(j+1,2),0) f(i,j,0)f(i+1,min(j+1,2),0)
    • k ≠ 0 k\neq 0 k=0case3 操作, f ( i , j , k ) → f ( i , j , k − 1 ) f(i,j,k)\rightarrow f(i,j,k-1) f(i,j,k)f(i,j,k1)
  • s i + 1 = ? s_{i+1}=? si+1=?,将上面两种情况都跑一遍即可。

code

#include <bits/stdc++.h>
using namespace std;
#define mod 1000000007
#define int long long
#define maxn 300005
char s[maxn];
int n, ans;
int f[maxn][3][3];

signed main() {
    scanf( "%s", s + 1 );
    n = strlen( s + 1 );
    f[0][0][0] = 1;
    //f[i][j][k]:到字符串第i位时 有j个1 k个0
    for( int i = 0;i < n;i ++ ) {
        if( s[i + 1] != '0' ) {
            for( int j = 0;j <= 2;j ++ ) {
                ( f[i + 1][j][0] += f[i][j][1] ) %= mod;
                ( f[i + 1][j][1] += f[i][j][2] ) %= mod;
                ( f[i + 1][min( j + 1, 2ll )][0] += f[i][j][0] ) %= mod;
            }
        }
        if( s[i + 1] != '1' ) {
            for( int j = 0;j <= 2;j ++ ) {
                ( f[i + 1][j][1] += f[i][j][2] ) %= mod;
                ( f[i + 1][j][2] += f[i][j][1] ) %= mod;
                ( f[i + 1][j][1] += f[i][j][0] ) %= mod;
            }
        }
    }
    for( int i = 0;i <= 2;i ++ )
        for( int j = 0;j <= i;j ++ )
            ( ans += f[n][i][j] ) %= mod;         
    printf( "%lld\n", ans );
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值