【题解】AND, OR, Sort!
题目链接
https://codeforces.com/contest/2266/problem/C
题目大意
给定一个长度为 \(n\) 的二进制字符串 \(s\)(仅包含字符 0 和 1)。你可以执行以下操作任意次(可以为 0 次):
- 选择一个位置 \(i\)(\(1 \le i \le n\)),并将字符 \(s_i\) 替换为前缀 \(s_1, s_2, \dots, s_i\) 的按位与(bitwise AND)或按位或(bitwise OR)。
求将字符串调整为非递减序(即形式为 \(00\dots0011\dots11\))所需的最少操作次数。
解题思路与技巧
1. 技巧一:目标状态反推(Target State Characterization)
不从繁杂的操作去正向搜索,而是分析合法终态的几何特征。
排好序的 0-1 串必然形如 \(00\dots0011\dots11\),它实质上完全由一个分割点(Split Point)决定:分割点左侧全是 0,右侧全是 1。
2. 技巧二:挖掘关键不变量(Invariants)
- 首位字符不可变:
位置 \(1\)(下标 0)的前缀只有其自身,做 AND 或 OR 运算结果依然是它自己。因此,首字符永远无法被修改。 - 首字符为
1的情况:
既然首字符永远无法变成0,为了让整串非递减,整个字符串最终必须全部变为1。
由于首位就是1,后面的任何0都可以直接利用前缀 OR 变成1。此时的最小操作数,恰好等于整个串中0的总个数。
3. 技巧三:两阶段操作保序法(Constructive Order)
当首字符为 0 时,可以在任意位置 \(i\) 划分分界线(左边全变 0,右边全变 1)。
将区间 \([0, i]\) 变为 0、区间 \([i+1, n-1]\) 变为 1 的代价看似是:
\[\text{Cost} = (\text{左侧 } 1 \text{ 的个数}) + (\text{右侧 } 0 \text{ 的个数})
\]
那么这个代价在操作过程中是否会产生依赖冲突导致死锁呢?
答案是不会,只要按照以下两阶段顺序操作即可证明其充分性:
- 第一阶段(自左向右 OR 右侧):
如果右侧有0想变成1,只要左侧还保留着原有的1,我们先不动左侧的1,直接自左往右扫描右半段,利用前缀 OR 把所有目标为1的位置刷成1。 - 第二阶段(AND 清理左侧):
右侧处理完毕后,由于首位 \(s_0 = 0\) 始终存在,左半段的所有位置前缀中都含有0,此时对左侧原本为1的位置执行前缀 AND,即可顺利刷为0。
两步操作互不干扰,因此任意分割点下的代价计算均成立。
4. 技巧四:前缀/后缀计数线性转移
分割点共有 \(O(n)\) 个。不需要在每个分割点耗费 \(O(n)\) 暴力重算,只需先统计全串 0 的总数,然后用一个循环从左向右滑动分割点:
- 遇到
1:左侧需要变0的数量 \(+1\); - 遇到
0:右侧需要变1的数量 \(-1\); - 每次 \(O(1)\) 维护并更新最小值即可。
复杂度分析
- 时间复杂度:统计字符出现次数需要 \(O(n)\),滑动分界点遍历一次需要 \(O(n)\),单组用例时间复杂度为 \(O(n)\)。总复杂度为 \(O(\sum n) \le O(2 \cdot 10^5)\),效率极高。
- 空间复杂度:仅使用几个基础计数器,额外辅助空间复杂度为 \(O(1)\)。
C++ 完整代码
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
void solve() {
int n;
cin >> n;
string s;
cin >> s;
// 统计整个字符串中 '0' 的总数量
int zeros_right = 0;
for (int i = 0; i < n; i++) {
if (s[i] == '0') {
zeros_right++;
}
}
// 若首字符为 '1',由于首位无法改变,最终串必须全为 '1'
// 代价就是把所有 '0' 变成 '1'
if (s[0] == '1') {
cout << zeros_right << "\n";
return;
}
// 首位为 '0':滑动维护分割点
int ones_left = 0;
int ans = 1e9;
// 枚举分割点:区间 [0, i] 置为 0,区间 [i + 1, n - 1] 置为 1
for (int i = 0; i < n; i++) {
if (s[i] == '1') {
ones_left++;
} else {
zeros_right--;
}
ans = min(ans, ones_left + zeros_right);
}
cout << ans << "\n";
}
int main() {
// 基础输入输出加速
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}

浙公网安备 33010602011771号