Balanced 01-String

Balanced 01-String

题目描述

小苯有一个长度为 $n$ 的字符串 $s$,只包含字符 $\texttt{'0'}$、$\texttt{'1'}$ 和 $\texttt{'?'}$。

他定义一个 $01$ 字符串是平衡的,当且仅当字符串中所有相邻两个字符相同的对数(即满足 $s_i = s_{i+1}$ 的 $i$ 的数量)是偶数。

(特别的,长度为 $1$ 的 $01$ 串必然是平衡的,因为此时相邻相同对数为 $0$,$0$ 也是偶数。)

小苯可以将每个 $\texttt{'?'}$ 替换为 $\texttt{'0'}$ 或 $\texttt{'1'}$。你的任务是求出,在所有可能的替换结果中,有多少个 $01$ 字符串是平衡的?(结果对 $998244353$ 取模。)

输入描述:

第一行一个整数 $T\ (1 \leqq T \leqq 10)$,表示测试数据组数。

接下来 $T$ 行,每行一个字符串 $s \ (1 \leqq |s| \leq 5 \times 10^5)$,只包含 $\texttt{'0'}$、$\texttt{'1'}$、$\texttt{'?'}$。

保证所有测试数据的字符串长度 $n$ 之和不超过 $5 \times 10^5$。

输出描述:

对于每组数据,输出一个整数表示满足条件的 $01$ 字符串数量对 $998244353$ 取模的结果。

示例1

输入

2
0?1
????

输出

0
8

说明

第一组数据:$s = \texttt{" 0?1"}$,两种替换:

$\texttt{"001"}$:相邻相同对数为 $1$($\texttt{"00"}$),奇数,不平衡。

$\texttt{"011"}$:相邻相同对数为 $1$($\texttt{"11"}$),奇数,不平衡。

输出 $0$。

 

解题思路

  先给出容易想到的一般做法,直接无脑考虑 dp 就行了。定义 $f(i,j,k)$ 表示前 $i$ 个字符中,第 $i$ 个字符为 $j \in \{0,1\}$ ,且相邻相同对数的奇偶性为 $k \in \{0,1\}$ 时的方案数。根据第 $i-1$ 个字符的类型进行状态转移,状态转移方程为 $$f(i,j,k) = \sum\limits_{j=0}^{1}{[s_i=j]\sum\limits_{k=0}^{1}{f(i-1,0,[j=0] \oplus k) + f(i-1,1,[j=1] \oplus k)}}$$

  需要注意的是,当 $s_i = \texttt{?}$ 时,$s_i$ 既可以取 $0$ 或 $1$。最后要求的答案就是 $f(n,0,0) + f(n,1,0)$。

  AC 代码如下,时间复杂度为 $O(n)$:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 5e5 + 5, mod = 998244353;

char s[N];
int f[N][2][2];

void solve() {
    cin >> s;
    int n = strlen(s);
    memset(f[0], 0, sizeof(f[0]));
    if (s[0] == '0' || s[0] == '?') f[0][0][0] = 1;
    if (s[0] == '1' || s[0] == '?') f[0][1][0] = 1;
    for (int i = 1; i < n; i++) {
        memset(f[i], 0, sizeof(f[0]));
        for (int j = 0; j <= 1; j++) {
            if (s[i] != '?' && s[i] - '0' != j) continue;
            for (int k = 0; k <= 1; k++) {
                for (int u = 0; u <= 1; u++) {
                    f[i][j][k] = (f[i][j][k] + f[i - 1][u][(j == u) ^ k]) % mod;
                }
            }
        }
    }
    cout << (f[n - 1][0][0] + f[n - 1][1][0]) % mod << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    
    return 0;
}

  下面给出本题的一个很巧妙的做法。容易知道长度为 $n$ 的字符串有 $n-1$对相邻位置。假设其中相同对数的数量为 $a$,不同对数的数量为 $b$,那么有 $a+b=n-1$。由于要求 $a$ 为偶数,即 $n-1-b$ 为偶数,等价于 $b$ 与 $n-1$ 的奇偶性相同,即 $b \equiv n-1 \pmod{2}$。其中 $n-1$ 是定值,因此 $(n-1) \bmod 2$ 是确定的。

  而 $b \bmod 2 = \left( \sum\limits_{i=1}^{n-1}{[s_i \ne s_{i+1}]} \right) \bmod 2$。由于最终的 $s_i$ 只有 $0$ 和 $1$ 两种取值,因此 $[s_i \ne s_{i+1}] = s_i \oplus s_{i+1}$,所以 $\left( \sum\limits_{i=1}^{n-1}{[s_i \ne s_{i+1}]} \right) \bmod 2 = \left( \sum\limits_{i=1}^{n-1}{s_i \oplus s_{i+1}} \right) \bmod 2$。其中每一项成对出现并抵消,且在模 $2$ 加法下求和等价于逐项异或,因此最终有 $b \bmod 2 = s_1 \oplus s_n$。所以 $a$ 是偶数等价于 $s_1 \oplus s_n = (n-1) \bmod 2$。

  这也意味着该条件的满足只与 $s$ 首尾两个字符的取值有关,而与中间字符 $s_2, s_3, \dots, s_{n-1}$ 无关。只要首尾固定后,无论中间取什么值,$b$ 的奇偶性都是一样的。因此假设中间位置有 $c$ 个 $\texttt{?}$,那么就会贡献 $2^c$ 种方案。

  所以我们只需枚举首尾字符所有可能的取值(若为 $\texttt{?}$ 则可取 $0$ 和 $1$,否则只能取确定的值),对于每一种合法的首尾组合,若其异或值恰好等于 $(n-1) \bmod 2$,则将 $2^c$ 累加到答案。

  AC 代码如下,时间复杂度为 $O(n)$:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int mod = 998244353;

void solve() {
    string s;
    cin >> s;
    int p = 1;
    for (int i = count(s.begin() + 1, s.begin() + max<int>(1, s.size() - 1), '?'); i; i--) {
        p = 2 * p % mod;
    }
    int ret = 0;
    for (char i = '0'; i <= '1'; i++) {
        if (s[0] != '?' && s[0] != i) continue;
        for (char j = '0'; j <= '1'; j++) {
            if (s.back() != '?' && s.back() != j) continue;
            if (~(s.size() - 1 - (i ^ j)) & 1) ret = (ret + p) % mod;
        }
    }
    cout << ret << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    
    return 0;
}

 

参考资料

  牛客周赛127题目讲解:https://www.bilibili.com/video/BV1qoksBzEtg/

posted @ 2026-01-21 23:19  onlyblues  阅读(0)  评论(0)    收藏  举报
Web Analytics