公式

G - K-th Nearest 解説 by yuto1115

解説

本問題のように「\(k\) 番目に○○な△△を求める」という問題では、しばしば二分探索が解法の初手として用いられます。実際、本問題においても二分探索を用いることができます。具体的には、\(f_j(x)=(\)\(A_1,A_2,\dots,A_N\) のうち点 \(B_j\) との距離が \(x\) 以下であるようなものの数\()\) と定めると、「点 \(A_1,A_2,\dots,A_N\) のうち点 \(B_j\) との距離が \(k_j\) 番目に近い点を求める」という問題は「\(f_j(x)\geq k_j\) を満たす最小の \(x\) を求める」という問題に帰着されます。\(f_j(x)\) には明らかに単調性があることから、二分探索を用いれば、「ある \(j,x\) に対して \(f_j(x)\) を求める」というクエリを \(O(Q\log A)\) 回(ここで \(A\) は座標としてありうる数の範囲)処理することで本問題を解くことができます。

「ある \(j,x\) に対して \(f_j(x)\) を求める」すなわち「\(b_j-x\) 以上 \(b_j+x\) 以下の範囲内にある \(a\) の要素数を求める」という問題は、最初に \(a\) をソートしておけば、これもまた二分探索によって高速に処理することができます。

全体の計算量は \(O(N\log N + Q\log A\log N)\) です。

実装例 (C++) :

#include <bits/stdc++.h>

using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, q;
    cin >> n >> q;
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    sort(a.begin(), a.end());
    while (q--) {
        int b, k;
        cin >> b >> k;
        auto f = [&](int x) -> bool {
            // (# of points in [b-x, b+x]) >= k
            auto lb = lower_bound(a.begin(), a.end(), b - x);
            auto ub = upper_bound(a.begin(), a.end(), b + x);
            return ub - lb >= k;
        };
        int ng = -1, ok = (int) 2e8 + 10;
        while (ok - ng > 1) {
            int mid = (ok + ng) / 2;
            if (f(mid)) ok = mid;
            else ng = mid;
        }
        cout << ok << '\n';
    }
}

投稿日時:
最終更新: