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

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

JOI 二次予選 2023 B - ジョイ四人組 (AOJ 0748) (2Q, 難易度 5)

「最大値と最小値の差」を最小化せよと言われたら、最大値または最小値を固定すると上手くいくことが多い!

問題概要

サイズが  N であるような 4 つの数列  A, B, C, D が与えられる。

これらの数列から 1 個ずつ要素を選んで  a, b, c, d とする。 \max(a, b, c, d) - \min(a, b, c, d) の値の最小値を求めよ。

制約

  •  1 \le N \le 75000

考えたこと

愚直に全探索すると、 O(N^{4}) の計算量となって間に合わないので高速化しましょう。

高速化したいと考えたとき、よく考えることは「ある変数の値を固定してみる」ということです。たとえば、数列  A から選ぶ要素  a を固定する、などが考えられます。しかしこのとき、残りの数列  B, C, D に対して、 a より大きい数を選べば良いのか、 a より小さい数を選べば良いのかが判然としないのです。(8 通り試す手もある)

このように「最大値と最小値の差」を最小化したいという問題では、上を抑えるのと下を抑えるのと同時に考えないといけなくて困ることが多いですね。

そこで、「最大値」と「最小値」のうち、「最小値」を固定してしまうという解法が思い浮かびます。最小値  m が決まれば、残りの数は、 m 以上の範囲でできる限り小さい数を選ぶことに集中できるのです。

最小値を固定する

さて、4 つの数列から 1 個ずつ選んでできる 4 数の最小値として考えられる値は  4N 通りあります。つまり、

  • どの数列から選ぶか(4 通り)
  • その数列のどの要素を選ぶか( N 通り)

をかけ合わせて  4N 通りです。

そして、その最小値を  v としたとき、各数列に対して  v 以上の最小の整数  w を求めて、 w-v の値の最大値を求めます。(これは  O(\log N) の計算量で求められます)

この値が最小となるような  v を探索しましょう。

全体として、 O(N \log N) の計算量で求められます。

コード

#include <bits/stdc++.h>
using namespace std;
const long long INF = 1LL << 60;

int main() {
    int N;
    cin >> N;
    vector<vector<long long>> A(4, vector<long long>(N));
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < N; j++) cin >> A[i][j];
        A[i].push_back(INF);
        sort(A[i].begin(), A[i].end());
    }

    long long res = INF;
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < N; j++) {
            // 最小値が A[i][j] である場合
            long long tmp = 0;
            for (int k = 0; k < 4; k++) {
                long long v = *lower_bound(A[k].begin(), A[k].end(), A[i][j]);
                tmp = max(tmp, v - A[i][j]);
            }
            res = min(res, tmp);
        }
    }
    cout << res << endl;
}