一篇题解

# [AGC045B] 01 Unbalanced

# 题意:

给定一个字符串,其中 ?? 可以替换成 0011。让最小化 0011 的数量差。

# 分析:

一般对于最小化最大值的题,往往都可以用二分去做,(但是这道题的二分的 check 函数我不会写),那么就想一想有没有什么平替,我们可以想到贪心和 dp 也是可以解决类似的问题的,我们可以考虑因为是跟个数有关且只有两个不同的数,那么我们就可以将 00 看成是 1-111+1+1,然后就转换成了最小化最大的累积和与最小的累积和的差值。先可以设,在累计和最大值不超过 limitlimit 时,flimitf_{limit} 为最大累计和的最小值,gg 为最小累计和的最大值,那么我们的答案就是求 min{limitflimit}(glimit)\min \left\{limit - f_{limit}\right\}(g\leq limit)

然后我们需要去求 gg,对于求 gg,我们可以贪心的去求解,在一开始我们先将所以的问号都设置成 1-1,然后在检查一遍,如果累计和不超过我们的 limitlimit 的话,就将 1-1 变成 11

那么这样贪心为什么是正确的呢?

limitlimit 增加 22 时,最多只有一个 ?? 发生变换,所以 flimit+2flimit+2f_{limit+2}\leq f_{limit}+2,那么 limitflimit+2(limit+2)(flimit+2)limit-f_{limit+2}\leq (limit+2)-(f_{limit}+2),那么我们最终的答案为 min{limitflimit}min{(limit+2)flimit+2}\min \left\{limit-f_{limit} \right\} \leq \min \left\{ (limit+2)-f_{limit+2} \right\}

那么正确性保证了,最后的答案也就是 min(gfg,(g+1)fg+1)\min(g-f_{g},(g+1)-f_{g+1})

代码实现起来也是比较容易的,一些细节也在代码中标注了。

#include <cstring>
#include <iostream>
#include <algorithm>
namespace read_write   // 快读快写可以忽略
{
    template <typename T>
    inline void read(T &x)
    {
        x = 0;
        T f = 1;
        char ch = getchar();
        while (ch > '9' || ch < '0')
        {
            if (ch == '-')
                f = -1;
            ch = getchar();
        }
            while (ch >= '0' && ch <= '9')
                x = x * 10 + ch - '0', ch = getchar();
        x *= f;
        return;
    }
    template <typename T>
    inline void write(T x)
    {
        if (x < 0)
        {
            x = -x;
            putchar('-');
        }
        if (x > 9)
            write(x / 10);
        putchar(x % 10 + '0');
    }
    template <typename T>
    T max(T x, T y)
    {
        return x > y ? x : y;
    }
    template <typename T>
    T min(T x, T y)
    {
        return x > y ? y : x;
    }
    template <typename T>
    void swap(T &a, T &b)
    {
        T tem = b;
        b = a;
        a = tem;
    }
}
namespace Solve
{
    using namespace read_write;
    const int N = 1e6 + 10;
    std::string a;
    int sum[N];   // 记录后缀中 1 的个数 (与 0 抵消之后的个数)
    int len;
    int calc(int limit)
    {
        int cnt, mn;
        cnt = mn = 0;
        for(int i = 1 ; i <= len ; ++ i)
        {
            if(a[i] == '0')
                cnt --;
            else if(a[i] == '1')
                cnt ++ ;
            else 
            {
                if(cnt + sum[i + 1] + 1 <= limit)
                    cnt ++;
                else 
                    cnt --;
            }
            mn = min(mn, cnt);    // 计算 f [g]
        }
        return limit - mn;
    }
    void solve()
    {
        std::cin >> a;
        len = a.size();
        a = ' ' + a;   // 在字符串前加个空格,调整下标(个人习惯
        for(int i = len ; i >= 1 ; -- i)
            sum[i] = max(0, sum[i + 1] + (a[i] == '1' ? 1 : -1));   // 在计算 1 的个数时也将 '?' 看成是 0
        write(min(calc(sum[1]), calc(sum[1] + 1)));   // 加 1 为了防止 sum [1] 为 0 时,出些奇怪的错误
    }
}
int main()
{
    Solve::solve();
    return 0;
}
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Tokai Teio 微信支付

微信支付

Tokai Teio 支付宝

支付宝

Tokai Teio 贝宝

贝宝