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

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

AtCoder ABC 306 C - Centers (灰色, 250 点)

バケットを使ってもいいし、set や map を使ってもいいかもしれない

問題概要

 1, 2, \dots, N が 3 回ずつ表れる長さ  3N の数列  A_{1}, A_{2}, \dots, A_{3N} が与えられる。

 1, 2, \dots, N を「数列  A において 2 回目にその値が登場する index」が小さい順にソートせよ。

制約

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

考察:まず問題を掴む

最初の注意点として、配列 A の添字は 0 始まりで考えることにする。

まずは問題をつかもう。たとえば  A = (1 1 3 2 3 2 2 3 1) のとき、

  •  1 が 2 回目に登場するのは A[1]
  •  2 が 2 回目に登場するのは A[6]
  •  3 が 2 回目に登場するのは A[4]

ということで、これが小さい順に  1, 2, 3 を並べると、答えは  1, 3, 2 となる。

解法 (1):2 回目に登場する index を求めて、キーにしてソート

一つ目の方法として、次の方法が考えられる。まず、次のような配列 ind を求める。


ind[v]A[i] = v となる添字 i は 3 個あるが、そのうち 2 番目の値


とする。この配列は for 文を回すことで求められる。たとえば、次のようにすればよい。

vector<int> cnt(N+1, 0);  // cnt[i] は i が何回登場したかを表す
for (int i = 0; i < N * 3; ++i) {
    ++cnt[A[i]];
    if (cnt[A[i]] == 2) {
        ind[A[i]] = i;
    }
}

このようにして配列 ind を求めれば、その値をキーとして、 1, 2, \dots, N をソートすればよい。

計算量はソートの部分が最も時間がかかるため、 O(N \log N) となる。

コード

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

int main() {
    int N;
    cin >> N;
    vector<int> cnt(N+1, 0), ind(N+1);
    for (int i = 0; i < N * 3; ++i) {
        int A;  cin >> A;
        ++cnt[A];
        if (cnt[A] == 2) ind[A] = i;
    }
    
    // 1, 2, ..., N を ind が小さい順に並び替える
    vector<int> res(N);
    for (int i = 0; i < N; ++i) res[i] = i+1;  // 配列 {1, 2, ..., N} を作る
    sort(res.begin(), res.end(), [&](int i, int j){return ind[i] < ind[j];});
    for (int i = 0; i < N; ++i) cout << res[i] << " ";
    cout << endl;
}

解法 (2):2 番目に見つけるたびに res に push

もう少し楽に実装することもできる。

そもそも、配列 A を順に見ていく過程で、「2 回目に登場した値」を検出した順に、答えを表す配列 res にその値を push していけばよい。

それを出力するだけで OK。

コード

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

int main() {
    int N;
    cin >> N;
    vector<int> cnt(N+1, 0), res;
    for (int i = 0; i < N * 3; ++i) {
        int A;  cin >> A;
        ++cnt[A];
        if (cnt[A] == 2) res.push_back(A);
    }
    
    for (auto v : res) cout << v << " ";
    cout << endl;
}