二分探索法の教育的典型問題ですね!!
問題概要
正の整数からなる長さ の数列が 2 つ ( と ) 与えられます。
各 に対して を計算することで得られる 個の整数を考えます。
これらの整数を小さい順に並べたとき、 番目 (1-indexed) に来る値を求めなさい。
制約
二分探索法へ帰着
単純に 個の値を計算して、それらを小さい順にソートする方法が最初に考えられるかもしれません。しかしこれでは の計算量を必要としてしまいます。
ここで「 番目に小さい値を求めなさい」という問題に対しては二分探索法が有効になることがある、ということを思い出してみましょう。求めたい値は、次の判定問題の答えが "Yes" となる最小の値 に他ならないのです。
個の整数のうち、 以下の値が 個以上あるかどうかを判定せよ
それではこの判定問題を解く方法を考えてみましょう。
判定問題
という 2 つの添字について考える問題では、どちらか 1 つの添字を固定して考えることが有効です。ここでは、 を固定して考えてみましょう。
つまり、各 に対して、 の値が 以下となる の個数を数えていくことにします (それらの総和をとればよいです)。これは次の問題と等価です。
のうち、 以下であるものは何個あるか
この問題を解くためには、ふたたび二分探索 (lower_bound) が活用できます。具体的には、数列 をあらかじめソートしておくことを前提として
upper_bound(b.begin(), b.end(), x / a[i]) - b.begin()
によって求められます。
コード
以上の解法は、次のように実装できます。
最後に計算量を見積もります。まず判定問題を解くのに要する計算量は となります。そして判定回数は、 として、 と評価できます。よって全体の計算量は と評価できます。
#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { long long N, K; cin >> N >> K; vector<long long> a(N), b(N); for (int i = 0; i < N; ++i) cin >> a[i]; for (int i = 0; i < N; ++i) cin >> b[i]; // b はあらかじめソートしておく sort(b.begin(), b.end()); // 判定問題 auto check = [&](long long x) -> bool { long long cnt = 0; for (int i = 0; i < N; ++i) { cnt += upper_bound(b.begin(), b.end(), x / a[i]) - b.begin(); } return (cnt >= K); }; // 二分探索 (right は十分大きな値に設定) long long left = 0, right = 1LL<<60; while (right - left > 1) { long long mid = (left + right) / 2; if (check(mid)) right = mid; else left = mid; } cout << right << endl; }