Official

C - Counting 2 Editorial by blackyuki


各クエリについて愚直に答えを計算すると、計算量 \(O(QN)\) で実行時間超過(TLE)してしまいます。

各クエリについて高速に答えを計算する方法を考えます。

前計算として、\(N\) 人を身長の昇順にソートしておきます。

昇順にソートされた長さ \(N\) の数列 \(A\) と整数 \(x\) が与えられた時、二分探索を用いると計算量 \(O(\log N)\)\(x \leq A_i\) を満たす最小の \(i\) を求めることができます。以下は二分探索の概要です。

求めたい \(i\) が区間 \([l,r]\) にあることが分かっているとする(初期状態では \(l = 0, r = N\) など)。

\(md =\left\lfloor \dfrac{l + r}{2}\right\rfloor\) として、\(x \leq A_{md}\) のとき求めたい \(i\) は区間 \([l,md]\) にあり、そうでない場合区間 \([md+1,r]\) にあることが分かる。

\(1\) 回のステップで区間の長さが半分以下になるから、全体で \(O(\log N)\) のステップ数で区間の長さが \(1\) となり、求めたい \(i\) が求められる。

なお、多くの言語では二分探索の標準ライブラリが提供されているので、それを用いると簡潔に実装することができます。

実装例1 (C++)

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

int main(){
    int n, q; cin >> n >> q;
    vector<int> v(n);
    for(int i = 0; i < n; i++) cin >> v[i];
    sort(v.begin(),v.end());
    for(int i = 0; i < q; i++){
        int x; cin >> x;
        int ok = n, ng = -1; // v[ok] >= x, v[ng] < x であることが分かっている
        while(ok - ng > 1){
            int md = (ok + ng) / 2;
            if(v[md] >= x) ok = md;
            else ng = md;
        }
        cout << n - ok << endl;
    }
}

実装例2 (C++)

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

int main(){
    int n, q; cin >> n >> q;
    vector<int> v(n);
    for(int i = 0; i < n; i++) cin >> v[i];
    sort(v.begin(),v.end());
    for(int i = 0; i < q; i++){
        int x; cin >> x;
        cout << v.end() - lower_bound(v.begin(), v.end(), x) << endl;
    }
}

posted:
last update: