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/