TechFUL: Improving Binary

問題概要

バイナリ文字列  S が与えられる.

文字列の良さとは、1 が連続して並んでいる区間の長さの最大値である.

以下の操作を  K 回まで行える.

  •  S の文字が 0 である箇所を 1 つ選び、1 に変える.

適切に操作したときの文字列の良さの最大値を求めよ.

問題のリンク

制約

  •  S は 0 または 1 からなる文字列

  •  1 \leq | S | \leq 10^{5}

  •  0 \leq K \leq (S に含まれる 0 の個数)

解法1: 二分探索

 0 \leq i \lt | S | について、位置  i を始点に右に 1 ができるだけ長く連なるように操作を行う.

最初に文字 0 が現れる位置を  r_{i} とする.

このとき、 \displaystyle \max_{0 \leq i \lt | S |} r_{i} - i が答えである.

例として、S = 001001011, K = 2 の場合をみると

  • 位置 0 を始点とした場合、S = 001001011 の位置 0, 1 の 0 を 1 に変えれば、
    S = 111001011 となり、 r_{0} = 3 となる.

  • 位置 1 を始点とした場合、S = 001001011 の位置 1, 3 の 0 を 1 に変えれば、
    S = 011101011 となり、 r_{1} = 4 となる.

  • 位置 2 を始点とした場合、S = 001001011 の位置 3, 4 の 0 を 1 に変えれば、
    S = 001111011 となり、 r_{2} = 6 となる.

次に  r_{i} の計算であるが、 これは  S_{0} ... S_{j} に文字 0 がいくつ存在するか数える配列  cnt_{j} を事前に計算しておけば、 二分探索を使用して  O(\log | S |) 時間でできる.

#include <bits/stdc++.h>
using namespace std;

string S;
int K;
int cnt[100010];

int main() {
    cin >> S >> K;

    for (int i = 0; i < S.size(); ++i) {
        cnt[i + 1] = cnt[i] + (S[i] == '0');
    }

    int ans = 0;
    for (int i = 0; i < S.size(); ++i) {
        int ok = i, ng = S.size() + 1;
        while (ng - ok > 1) {
            int m = (ok + ng) / 2;
            if (cnt[m] - cnt[i] <= K) ok = m;
            else ng = m;
        }
        ans = max(ans, ok - i);
    }
    cout << ans << endl;
}

解法2: しゃくとり法

上の  r_{i} の値は  1 \leq i \lt | S | において  r_{i-1} \leq r_{i} が成り立つ.

そのため、 r_{i} はしゃくとり法を使って全体で  O(| S |) 時間で計算できる.

#include <bits/stdc++.h>
using namespace std;

string S;
int K;
int cnt[100010];

int main() {
    cin >> S >> K;

    for (int i = 0; i < S.size(); ++i) {
        cnt[i + 1] = cnt[i] + (S[i] == '0');
    }

    int ans = 0, j = 0;
    for (int i = 0; i < S.size(); ++i) {
        while (j <= S.size() && cnt[j] - cnt[i] <= K) {
            j++;
        }
        ans = max(ans, j - 1 - i);
    }
    cout << ans << endl;
}