原题题面
题目描述
每次给你一个 字符串 。
现在需要将字符串分为前中后三段(段长可为 ),每种分法都有一个消耗值,消耗值定义为前端和后端中 的数量与中段中 的数量的最大值。
问采用哪种分法可使消耗最小,输出这个值。
输入样例
5
101110110
1001001001001
0000111111
00000
1111
输出样例
1
3
0
0
0
题目分析
这个字符串题目有多种解法,接下来我们介绍三种方式。
- 二分法
我们依次枚举中段的左端点并固定,然后二分中段的右端点。
对于每个右端点,当其向左移动时,中段的 数量一定呈现非递增状态,前后段的 数量一定呈现非递减状态,而且每次移动一定会导致两者中一者的数量变化。
对此我们以将当前分法的中段 数量和前后端的 数量作为比较函数,直到两者差距最小的时候,二者的最大值才会最小。
最后对所有的分段最大值取最小,得到答案。
- 双指针法
在上述二分法的思想下,我们发现对于中段的左右端点 ,若仅向右移动 ,则 的数量非递减, 的数量非递增,且 和 数量的相对差值变大,若要保持二者的差值最小,需要向右移动 。
同时对 数量的统计可以是:
的数量为字符串 的总数减去中段中 的数量
的数量为中段长度减去中段中 的数量。
这样便可以只统计 的前缀和得到两个数值。
- 推导优化法
在上述方法的思想下,我们对求答案的公式进行推导变化。
对于固定一个端点的中段:
我们知道中段长度与中段 数量有一定的正相关关系,且随着中段增长或缩减,中段长度的变化量大于等于中段 数量的变化量。
当中段长度小于 的总数时,随着中段长度的增加, 一定非递增。
当中段长度大于 的总数时,随着中段长度的增加, 一定非递减。
可知二者相等时,答案一定最优。
因此,我们依次枚举中段的左端点,且中段长度为 的数量,便可得到答案最优解。
Accept代码
// 双指针
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int t; cin >> t;
while (t --)
{
string s; cin >> s;
int n = s.size();
int s0 = 0, s1 = count(s.begin(), s.end(), '1');
int res = 2e5;
for (int l = 0, r = -1; l < n; l ++)
{
while (s0 < s1 && r < n - 1)
{
r ++;
if (s[r] == '0') s0 ++;
else s1 --;
}
res = min(res, max(s0, s1));
if (s[l] == '0') s0 --;
else s1 ++;
}
cout << res << "\n";
}
return 0;
}
// 推导优化
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int t; cin >> t;
while (t --)
{
string s; cin >> s;
int n = s.size(); s = " " + s;
vector<int> f(n + 1);
for (int i = 1; i <= n; i ++)
f[i] = f[i - 1] + (s[i] == '1');
int res = 2e5;
for (int i = f[n]; i <= n; i ++)
res = min(res, f[n] - (f[i] - f[i - f[n]]));
cout << res << "\n";
}
return 0;
}