C0268 Count 1's 题解

一道很好的前缀和思维题,个人觉得是前四题最难的那个。

思路历程

一开始想的是,因为 \(0\)\(1\) 之间会相互抵消,所以我直接去找最大的连续的 \(1\) 或者 \(0\) 作为答案,但是又想到有可能两段 \(1\) 或者 \(0\) 可能会更优,所以又想到确定 \(1\)\(0\) 分别的左右区间,然后统计出这个区间内被 \(0\) 或者 \(1\) 抵消后还剩多少个 \(0\) 或者 \(1\)。但是我又漏算了可能可以直接在区间内得到更多答案的情况,举个例子:

假设抵消完后剩余的数字为 \(x\),那么在这段区间内是有可能出现一段连续的同样数字的长度为 \(y\) 并使得 \(y > x\)

然后看了题解,是我没想到的思路:

不难发现对于 \(1\) 而言,反转等于得分 \(-1\),对于 \(0\) 而言则恰恰相反。所以我们直接暴力将所有 \(0\)\(1\) 变更为上面的值,然后做前缀和,发现前缀和的含义就变成了到 \(i\) 位时全部反转的得分。

那么我们不难想到直接利用统计最大前缀和和最小前缀和做差分去得到变化值得上界和下界,并且注意到数组只会有 \(0\)\(1\),意味着我们可以一点点的移动区间边界得到下界到上界的所有值,所以最后的答案就是上界-下界+1。

#include <bits/stdc++.h>
#include <limits.h>
using namespace std;
#define ll long long
#define ull unsigned long long

void solve () {
    ll n; cin >> n;
    vector <ll> a(n+1), sum(n+1);
    for (int i = 1;i <= n;i++) {
        cin >> a[i];
        sum[i] = sum[i-1] + (a[i] ? -1 : 1);
    } 
    ll minn = 0, maxn = 0, ansa = 0, ansb = 0;
    // minn = sum[1], maxn = sum[1];
    // ansa = minn, ansb = minn;
    for (int i = 1;i <= n;i++) {
        ansa = max(ansa, sum[i] - minn);
        ansb = min(ansb, sum[i] - maxn);
        maxn = max(maxn, sum[i]);
        minn = min(minn, sum[i]);
    }
    cout << (ansa - ansb + 1) << "\n";
}

int main () {
    freopen("proposals.in", "r", stdin);
    freopen("proposals.out", "w", stdout);
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int _ = 1;
    while (_--) solve();
    return 0;
}