Binary String(字符串)

317 阅读3分钟

Problem - 1680C - Codeforces

原题题面

image.png

题目描述

每次给你一个 0101 字符串 ss

现在需要将字符串分为前中后三段(段长可为 00),每种分法都有一个消耗值,消耗值定义为前端和后端中 11 的数量与中段中 00 的数量的最大值。

问采用哪种分法可使消耗最小,输出这个值。

输入样例

5
101110110
1001001001001
0000111111
00000
1111

输出样例

1
3
0
0
0

题目分析

这个字符串题目有多种解法,接下来我们介绍三种方式。

  • 二分法

我们依次枚举中段的左端点并固定,然后二分中段的右端点。

对于每个右端点,当其向左移动时,中段的 00 数量一定呈现非递增状态,前后段的 11 数量一定呈现非递减状态,而且每次移动一定会导致两者中一者的数量变化。

对此我们以将当前分法的中段 00 数量和前后端的 11 数量作为比较函数,直到两者差距最小的时候,二者的最大值才会最小。

最后对所有的分段最大值取最小,得到答案。

  • 双指针法

在上述二分法的思想下,我们发现对于中段的左右端点 l,rl,r,若仅向右移动 ll,则 11 的数量非递减,00 的数量非递增,且 1100 数量的相对差值变大,若要保持二者的差值最小,需要向右移动 rr

同时对 0,10,1 数量的统计可以是:

11 的数量为字符串 11 的总数减去中段中 11 的数量

00 的数量为中段长度减去中段中 11 的数量。

这样便可以只统计 11 的前缀和得到两个数值。

  • 推导优化法

在上述方法的思想下,我们对求答案的公式进行推导变化。

对于固定一个端点的中段:

res=max(中段0数量,前后段1数量)=max(中段长度,1的总数)中段1数量res = max(中段0数量,前后段1数量) = max(中段长度,1的总数)-中段1数量

我们知道中段长度与中段 11 数量有一定的正相关关系,且随着中段增长或缩减,中段长度的变化量大于等于中段 11 数量的变化量。

当中段长度小于 11 的总数时,随着中段长度的增加,resres 一定非递增。

当中段长度大于 11 的总数时,随着中段长度的增加,resres 一定非递减。

可知二者相等时,答案一定最优。

因此,我们依次枚举中段的左端点,且中段长度为 11 的数量,便可得到答案最优解。

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;
}