ABC171のD問題に挑戦してみた

初めに

今回はABC171のD問題に挑戦してみました。
この問題は、解説を見ないで回答することができましたが、
解いていく中で気付きがあったのでそれを中心に記載して行きます。

https://atcoder.jp/contests/abc171

※ブログ内で問題の制約事項をすべて記載はしないので、制約事項は↑のURLより参照願います。

D - Replacing

N個の整数A_1~A_Nからなら数列が与えられます。その数列に対して1 → 2といった
数字の置き換え操作をQ回行い、1~Q回目の入れ替えのタイミングでの
数列の和をそれぞれ出力する問題です。

最初の回答
#include<bits/stdc++.h>

using namespace std;
using ll = long long;
const long long INF = 1LL<<60;

typedef pair<ll,ll> Pair;
int main(){

ll N,Q;

cin >> N;

vector<ll> A(N);

for(ll i = 0;i < N;++i) cin >> A[i];

cin >> Q;

ll B,C;
vector<ll> S(Q,0);

for(ll i = 0;i < Q;++i){

    cin >> B >> C;

    for(ll j = 0;j < N;++j){

        if(A[j] == B){
            A[j] = C;
        }

        S[i] += A[j];

    }

}

for(ll i = 0;i < Q;++i){

    cout << S[i] << endl;

}

return 0;

}
最終的な回答
#include<bits/stdc++.h>

using namespace std;
using ll = long long;
const long long INF = 1LL<<60;

typedef pair<ll,ll> Pair;
int main(){

ll N,Q;
ll sum = 0;

cin >> N;

ll A;
vector<ll> cnt(100001,0);

for(ll i = 0;i < N;++i){
    cin >> A;
    // 初期入力された数列の和を計算し格納
    sum += A;
    // バケットに入力された整数の個数を格納
    ++cnt[A];
}

cin >> Q;

ll B,C;
vector<ll> S(Q,0);

for(ll i = 0;i < Q;++i){

    cin >> B >> C;

    // 入れ替え操作が発生した際の差分(C-B)と入れ替わった数字の個数の積をsumに加算
    sum += (C - B)*cnt[B];
    // 入れ替え操作により入れ替えもとの数字の個数を入れ替え先の数字の個数に加算
    cnt[C] += cnt[B];
    // 入れ替え元の数字の個数を0に設定
    cnt[B] = 0;
    
    S[i] = sum;

}

for(ll i = 0;i < Q;++i) cout << S[i] << endl;

return 0;

}

まずこの問題は愚直に入れ替え操作を行うとO(NQ)となり、
N,Qの入力の制約が105のため、TLEになります。(最初の回答)

愚直に処理を行ってもTLEが発生することは分かったので、予め初期入力の総和を求め、
その総和に入れ替え時に発生した差分を加算するというアプローチで考えてみます。

差分の計算を行うためには、「B(入れ替え前の数字)」と「C(入れ替え後の数字)」を減算後に、
「入れ替え前の数字の数」と掛け算をすることで差分を求める事ができます。

ここで、各数字の個数を求めるためにバケット法という手法を利用します。

バケット法とは下記のように配列で「cnt[値] = 値の個数」で値の個数を集計する方法で、
数列の並びは重要では無く、個数が重要な場合に利用できる方法です。

f:id:hgm_blog:20200913112755p:plain
イメージ

バケット法を用いて配列cntに各数字の個数が求められているので、
後は以下の操作を繰り返す事で答えを求める事が出来ます。

 ①総和と入れ替え時の差分((C-B)*cnt[i])の和を求める

 ②「入れ替え前の数字」の個数を「入れ替え後の数字」の個数に加算

 ③「入れ替え前の数字」の個数を0に設定

最後に

今回書いたバケット法は今まで名前はしらなかったのですが
使用したことがある手法でした。やはり名前が付いていた方が
頭の中からパッと引き出せるので良いですね。

こんな感じでACしたけど、気付きがあった問題も書いていきます!