けんちょんの競プロ精進記録

競プロの精進記録や小ネタを書いていきます

AtCoder ABC 380 C - Move Segment (5Q, 灰色, 300 点)

ランレングス圧縮で解いた

問題概要

0 と 1 からなる長さ  N の文字列  S が与えられる。この文字列の中での「1 の塊」のうち、左から  K 番目のものを、 K-1 番目のものの右に移動させよ。

例:"010011100011001" → "010011111000001"

制約

  •  1 \le N \le 5 \times 10^{5}

考えたこと

まず、文字列  S をランレングス圧縮しよう。そうすると、

"010011100011001"
→ (0, 1), (1, 1), (0, 2), (1, 3), (0, 3), (1, 2), (0, 2), (1, 0)

というようになる。ランレングス圧縮のやり方については、次の問題の公式解説を見るとよい。

atcoder.jp

さて、ランレングス圧縮できれば、ランレングス圧縮した列上で、 K 番目の「1 の塊」と、その左の「0 の塊」を swap すればよい。上の例では、

(0, 1), (1, 1), (0, 2), (1, 3), (0, 3), (1, 2), (0, 2), (1, 0)
→ (0, 1), (1, 1), (0, 2), (1, 3), (1, 2), (0, 3), (0, 2), (1, 0)

というようになる。こうして得られた列を再び 0-1 文字列に直せば OK。

計算量は  O(N) となる。

コード

#include <bits/stdc++.h>
using namespace std;
using pint = pair<int,int>;

int main() {
    int N, K;
    string S;
    cin >> N >> K >> S;

    vector<pint> lens;
    for (int i = 0; i < N; ) {
        int j = i;
        while (j < N && S[j] == S[i]) j++;
        lens.emplace_back((int)(S[i]-'0'), j - i);
        i = j;
    }

    int num = 0;
    for (int i = 0; i < lens.size(); i++) {
        if (lens[i].first == 1) {
            num++;
            if (num == K - 1) {
                swap(lens[i+1], lens[i+2]);
            }
        }
    }
    for (auto [val, num] : lens) {
        for (int i = 0; i < num; i++) cout << val;
    }
    cout << endl;
}