从右往左看每个位置的强制翻转决策,可化简为统计相邻字符变化次数,再看末位是否为 0。
OJ: luogu
题目 ID: P2708
难度:普及-
标签:贪心字符串模拟
日期: 2026-06-19 10:26
题意
给出一排 0/1 硬币,1 表示正面,0 表示反面。
一次操作可以把从第一个硬币开始的某个前缀整体翻面。
要求用最少操作次数把所有硬币都变成 1。
思路
这题从右往左看最清楚。
最直接的教学版写法如下:
cpp
// brute.cpp:从右往左真正模拟翻前缀,作为教学版和对拍基准程序。
#include <bits/stdc++.h>
using namespace std;
string s;
void flip_prefix(int right_pos) {
for (int i = 0; i <= right_pos; i++) {
if (s[i] == '0') {
s[i] = '1';
}
else {
s[i] = '0';
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> s;
int ans = 0;
int n = (int) s.size();
// 从右往左定死每个位置:如果当前位置是 0,只能翻到这里为止。
for (int i = n - 1; i >= 0; i--) {
if (s[i] == '0') {
flip_prefix(i);
ans++;
}
}
cout << ans << '\n';
return 0;
}如果当前考虑到位置 i:
- 它已经是
1,不用动; - 它是
0,那就只能立刻把前缀[0..i]翻掉,因为以后再也没机会改它。
这说明最优策略其实是被强制决定的。
再往前推一步,会发现答案可以直接线性统计:
- 每当相邻两个字符不同,答案加一;
- 如果最后一个字符是
0,答案再加一。
这样就不需要真的翻前缀了。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
string s;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> s;
int n = (int) s.size();
int ans = 0;
// 每次相邻字符变化,说明至少要多做一次翻转来切换当前前缀状态。
for (int i = 1; i < n; i++) {
if (s[i] != s[i - 1]) {
ans++;
}
}
// 最右端如果还是 0,最后还必须再翻一次。
if (s[n - 1] == '0') {
ans++;
}
cout << ans << '\n';
return 0;
}复杂度
- 时间复杂度:
- 空间复杂度:
总结
这题的关键是把“前缀翻转”换成“从右往左定位置”的思考方式。
一旦看到右边位置的决策是强制的,就很容易推到最终的线性统计公式。