hotaruの蛍雪日記

競プロとゲームをしています

Perform Easily - Codeforces Round #679 Div.2 C

問題へのリンク

Div.2 C問題なのに解けなかったよオヨヨ。 C問題は1438人、D問題は1664人が正解していてD問題が簡単そう。 コンテスト中はCに固執せずDにも目を通すべきだった。

このコンテストは問題にNARUTOのキャラクタが出てくる。 コドフォはよく問題文にキャラクタが登場する。

問題情報

  • 問題名: Perform Easily
  • コンテスト: Codeforces Round #679 (Div. 2, based on Technocup 2021 Elimination Round 1)
  • 問題番号: C(Div.1 A)

キーワード

二分探索木

問題概要

ギターに6本の弦と無限フレットがある。 フレットには左から順に番号がついている。 N個の音符を演奏しなければならない。 ある音符は重複あり6つのフレットで演奏できる。 N個すべての音符を演奏できるフレットの最大番号と最小番号の差の最小値は?

考えたこと

演奏で使うフレットはたかだか6N個。 あるフレットを使うときのフレットの最大番号と最小番号の差を、すべてのフレットで探索する。 フレットの最大番号と最小番号を高速で求めるため、演奏で使うN個のフレットをsetで管理する。 新しいフレットをsetに入れて、新しいフレットで演奏する音符を演奏するフレットを取り出す。 計算量はO(NlogN)。

コード

#include <algorithm>
#include <array>
#include <iostream>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <stack>
#include <vector>
using namespace std;
#define int long long
constexpr long long INF = 1000000000000000000LL + 8;
constexpr int N = 6;

signed main() {
    cin.tie(0);
    ios::sync_with_stdio(0);

    array<int, N> a;
    for (int i = 0; i < N; i++) cin >> a[i];
    int n;
    cin >> n;
    vector<int> b(n);
    for (int i = 0; i < n; i++) cin >> b[i];

    sort(a.begin(), a.end());
    sort(b.begin(), b.end());

    // fn[i]: 弾くべき音符を弾けるfret番号と音符番号のペア
    set<pair<int, int>> num_note;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < N; j++) {
            const int num = b[i] - a[j];
            num_note.emplace(make_pair(num, i));
        }
    }

    // use_nums[i]: 音符iを弾くfret番号
    vector<int> use_nums(n, -1);
    // 今使うfret番号と音符番号の集合 最大値と最小値を得るのに使う
    set<pair<int, int>> minmaxnums;

    int ans = INF;

    // use_noteを小さい順から使っていく
    for (auto &&f : num_note) {
        // fと同じ音符を弾くfret音符ペアをminmaxnumsから削除する
        if (use_nums[f.second] > 0) {
            minmaxnums.erase(
                minmaxnums.find(make_pair(use_nums[f.second], f.second)));
        }

        // use_numsとminmaxnumsにfを追加する
        use_nums[f.second] = f.first;
        minmaxnums.emplace(f);

        // n個全ての音符を弾けるなら、minmaxnumsを使ってansを更新
        if (minmaxnums.size() == n) {
            const int dist_nums =
                (*prev(minmaxnums.end())).first - (*minmaxnums.begin()).first;
            ans = min(dist_nums, ans);
        }
    }

    cout << ans << "\n";
}

感想

  • Div.2 C問題にしては難しい。
  • これ計算量O(N)に落ちるだろうな。
  • 答えを求める部分で、num_note, use_nums, minmaxnumsの3つのコンテナを使っているが、もっと簡潔に書けるのでは?
  • STLのsetが便利すぎる。
  • setの最後へのイテレータを書くときはprev(s.end())よりs.rbegin()がいい。