1 条题解
-
0
E. 完美切割 题解
一、题意理解
给定一个由
0、1、?组成的字符串 。将每个?独立替换为0或1后,得到一个二进制字符串。一个二进制字符串是完美的,当且仅当存在一个切割位置,将字符串分为非空前缀 和非空后缀 ,使得对于每个 (从 到 ),都有 。
即前缀的每一位都不小于后缀对应位置的每一位。
目标是计算能生成完美字符串的替换方案总数,对 取模。
二、完美字符串的充要条件
2.1 必要条件分析
逐位比较前缀 和后缀 ,要求 。由于字符只有
0和1,不等关系只能是 、 或 ,绝不能出现 (即前缀为0且后缀为1)。考虑切割位置。设前缀长度为 ,后缀长度为 ,其中 。
2.2 分类讨论
情况一: 以
1开头若 ,取 ,前缀为
"1",后缀从 开始。比较时只需比较 和 :
- 无论 是
0还是1,都有 。
因此以
1开头的任何二进制字符串都是完美的。
情况二: 以
0开头若 ,则切割位置 时,。
必须保证 ,即 ,这要求 。
因此后缀也必须以
0开头,即字符串中必须存在第二个0(位置 )。进一步,若存在第二个
0,可构造如下切割方案:- 令前缀包含第一个
0到第二个0之前的所有字符(即前缀形如0111...1,以0开头、后接若干1,最后一位是第二个0前的一个1); - 后缀从第二个
0开始。
比较时:
- 前缀的每一位要么是
0(第一位),要么是1(其余位); - 后缀的第一位是
0,其余位任意。
由于前缀第一位是
0,后缀第一位也是0,满足 ;前缀其余位都是1,后缀对应位置无论是0还是1都有 或 。因此该字符串是完美的。
情况三:唯一的
0在开头若 以
0开头,且后面所有字符都是1(即形如0111...1),则:- 无论怎么切割,后缀必然以
1开头(因为只有第一个字符是0); - 但前缀第一位是
0,需要 ,不可能满足。
因此形如
0111...1(一个0后全是1)的字符串是不完美的。
2.3 充要条件总结
$$\boxed{ s \text{ 是完美的} \iff \begin{cases} s_1 = 1, & \text{或} \\[4pt] s_1 = 0 \ \text{且} \ \exists j > 1 \text{ 使得 } s_j = 0 \end{cases} } $$等价地:不完美的字符串只有一种——以
0开头且后面全为1。
三、计数方法
设字符串总长度为 ,问号总数为 。
方法:分类讨论第一个字符
情况一:第一个字符最终为
1若 为
1或?(替换为1),则无论其余位置如何,字符串必然完美。此时:
- 第一个字符已被确定(若是
?则只有一种选择:变为1); - 剩余 个位置中的问号可以任意替换(每个
?有 种选择)。
设除第一个字符外有 个问号,则方案数为:
情况二:第一个字符最终为
0若 为
0或?(替换为0),则还需保证字符串不是0111...1的形式。即除第一个字符外,不能全为
1。- 若后面已经有一个确定的
0(非?的0),则无论如何替换?,字符串都不是0111...1,方案数为 ; - 若后面没有确定的
0(即所有?替换为1就会变成0111...1),则需排除全替换为1的那一种方案,方案数为 。
四、最终公式
设 ,其中 为除第一个字符外的问号数量。
$$\boxed{ \text{ans} = \begin{cases} 0, & \text{若 } s_1 \text{ 确定为 } 0 \text{ 且后面无任何 } 0 \text{(此时无法形成完美字符串,但这种情况} \\[2pt] & \quad \text{已包含在情况二的公式中} \\[6pt] pw, & \text{若 } s_1 = 1 \\[4pt] pw, & \text{若 } s_1 = 0 \text{ 且 } \exists j > 1, s_j = 0 \text{(确定的 } 0 \text{)} \\[4pt] pw - 1, & \text{若 } s_1 = 0 \text{ 且 } \forall j > 1, s_j \neq 0 \text{(没有确定的 } 0 \text{)} \end{cases} } $$用 可能为
?的统一表达式:令
$$\text{ans} = \begin{cases} pw, & \text{若第一位取 } 1 \\[4pt] pw - (1 \text{ 如果后面没有确定的 } 0 \text{ 否则 } 0), & \text{若第一位取 } 0 \end{cases} $$pw为除第一位外问号的任意替换方案数。最终答案为两种情况之和(若第一位是
?,两种情况都合法;若第一位确定,则只取对应情况)。
五、标程解析
const val MOD = 998244353; fun main() = repeat(readln().toInt()) { val s = readln() // 计算 2^{q'},q' 为除第一个字符外的问号数量 var pw = 1 repeat(s.drop(1).count { it == '?' }) { pw = pw * 2 % MOD } var ans = 0 // 情况一:第一个字符为 1 if (s[0] == '1' || s[0] == '?') { ans = (ans + pw) % MOD } // 情况二:第一个字符为 0 if (s[0] == '0' || s[0] == '?') { // 检查后面是否有确定的 0 val x = if (s.indexOf('0', 1) == -1) 1 else 0 ans = (ans + pw - x) % MOD } println((ans + MOD) % MOD) }步骤解析:
- 计算 ,其中 是
s[1..n-1]中?的数量。 - 若第一位可以是
1,累加 。 - 若第一位可以是
0:- 在
s[1..n-1]中查找是否有确定的0。 - 若无(
indexOf('0', 1) == -1),则需排除全1的情况,累加 ; - 若有,累加 。
- 在
- 输出答案(注意取模可能产生负数,加
MOD再取模)。
六、样例验证
样例 1:
"0?1"- ,第一位后有两个字符:
?和1,,。 - 第一位
0,可取0。 - 后面有确定的
0吗?s.indexOf('0', 1):位置 是?,位置 是1,没有确定的0→ 。 - 第一位取
0的方案:。 - 第一位取
1: 为0,无法取1。 - 答案 ✅
样例 2:
"011"- ,。
- 第一位
0,可取0。 - 后面有确定的
0吗?位置 是1,位置 是1,没有 → 。 - 答案 ✅
样例 3:
"????"- ,。
- 第一位
?:可取1()或0(后面无确定的0,)。 - 答案 ✅
样例 4:
"110?0"- :第一位后字符
"10?0"中?的数量 ,。 - 第一位
1,可取1:。 - 第一位不能取
0。 - 答案 ✅
样例 5:
"0?1?"- :第一位后
"?1?"中?数量 ,。 - 第一位
0,可取0。后面有确定的0吗?位置 是?,位置 是1,位置 是?,没有 → 。 - 第一位取
0方案:。 - 答案 ✅
七、代码实现(C++)
#include <bits/stdc++.h> using namespace std; const int MOD = 998244353; void solve() { string s; cin >> s; int n = s.size(); // 计算 2^{q'},q' 为除第一个字符外的问号数 long long pw = 1; for (int i = 1; i < n; i++) { if (s[i] == '?') { pw = pw * 2 % MOD; } } long long ans = 0; // 情况一:第一位为 1 if (s[0] == '1' || s[0] == '?') { ans = (ans + pw) % MOD; } // 情况二:第一位为 0 if (s[0] == '0' || s[0] == '?') { // 查找后面是否有确定的 0 bool has_zero = false; for (int i = 1; i < n; i++) { if (s[i] == '0') { has_zero = true; break; } } int x = has_zero ? 0 : 1; ans = (ans + pw - x + MOD) % MOD; } cout << ans << "\n"; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { solve(); } return 0; }
八、复杂度分析
- 每个测试用例需遍历字符串一次(计算 和查找
0),时间复杂度 。 - 所有测试用例的字符串总长 ,整体可行。
- 空间复杂度 额外空间。
- 无论 是
- 1
信息
- ID
- 6658
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 2
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者