C Binary String
点此进入题面
题意:
你有一个 字符串 s
由字符 0
和 1
组成。
你必须从 字符串的开头 删除几个(可能是 0
个)字符,然后从 字符串的末尾 删除几个(可能是 0
个)字符。
删除后,字符串可能会变为空。删除的成本 是 以下两个值 的 最大值:
- 字符串中 剩余的字符
0
的个数; - 从字符串中 删除的字符
1
的个数。
要求:算出可以达到的 最低移除成本 是多少?
思路:
一般地,对于此类求 “最大值最小” 的问题,我们可以考虑用 二分答案 的方法来做。
关于 二分答案 的学习,可以看看我之前设置的专栏:二分
二分答案 ans
(ans
表示的是 最低移除成本,显然这是 一定有解 的,且这个 解 ans
一定是唯一的)。
之后,对于 每个枚举值 ans
,我们使用使用 双指针 以 O(n)
的时间复杂度 遍历一遍字符串,
双指针遍历要求:
- 双指针的区间内的
0
的数目 不能超过ans
(即 不可以超过最大值,这是显然的,因为题目要求的是 “最大值最小化”)。 - 区间长度越长越好。
之后判断 双指针区间之外 的部分的 1
的数目 是否也满足 不超过 ans
,如果 是 的话,二分答案 中的 判定函数 check
就返回 true
,否则 返回 false
。
值得一提的是:(这其中的思想比较重要,比赛中比较常见,分别关于 二分 和 双指针)
- 在双指针扫描的过程当中,只要 存在 双指针所维护区间之外的部分 的
1
的数目 不超过ans
就行,check
函数 直接返回true
。 - 在 双指针遍历 的时候,我们还可以站在 另一个角度 理解这个做法:用 一个指针
i
每次定好 中间部分区间 的 右端点,另一个指针j
贪心选择移动 左端点。
代码:
#define _CRT_SECURE_NO_WARNINGS 1
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#include <bits/stdc++.h>
using namespace std;
//#define int long long
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vi;
typedef pair<int, int> pii;
typedef map<int, int> mi;
typedef vector<ll> vll;
typedef pair<ll, ll> pll;
#define pb push_back
#define pp pop_back
#define x first
#define y second
const int N = 2e5 + 10;
int len;
int s0[N], s1[N];
inline void Clear()
{
for (int i = 1; i <= len; ++i) s0[i] = s1[i] = 0;
}
bool check(int ans)
{
for (int i = 1, j = 0; i <= len; ++i)
{
while (j < i && s0[i] - s0[j] > ans) ++j;
if (s1[j] + s1[len] - s1[i] <= ans) return true;
}
return false;
}
signed main()
{
int T = 1; cin >> T;
while (T--)
{
Clear();
string s; cin >> s;
len = s.size();
s = " " + s;
for (int i = 1; i <= len; ++i)
{
s0[i] = s0[i - 1] + ((s[i] - '0') ^ 1);
s1[i] = s1[i - 1] + (s[i] - '0');
}
int l = 0, r = len;
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
cout << l << '\n';
}
return 0;
}