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

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

AtCoder ABC 373 E - How to Win the Election (2D, 水色, 500 点)

方針を立てるのは難しくないけど、とにかく重たい問題だった。

問題概要

 1, 2, \dots, N に対して合計  K 票が集まった。これらの人のうち条件「自分よりも多くの票を集めた人が  M 人未満」を満たす人が当選となる。

 K 票のうち途中まで開票されて、各人  i には  A_{i} 票が集まった。

各人  i について、条件「あと  x 票を追加すれば当選確実」を満たすような最小の整数  x を求めよ (存在しない場合は -1)。

制約

  •  1 \le M \le N \le 2 \times 10^{5}
  •  1 \le K \le 10^{12}

考えたこと

 R = K - \sum_{i=1}^{N} A_{i} とする (残り票数)。

コンテスト本番では、 M = N の場合に処理がおかしくなっていることに気づかず、デバッグに時間がかかってしまった。 M = N の場合は、全員が当選確実 (自分より票を集めるのは  N-1 人以下のため) なので、0 を  N 個出力すればよい。以降、 M \lt N としよう。

さて、 x がある値で条件を満たすならば、それより大きい値でも当然条件を満たす。このことから、その境界を求めるのに二分探索が使える。つまり、次の判定問題を解けば良い。この判定問題の答えが "Yes" となる最小の  x を求めれば良いのだ。


 A_{1}, A_{2}, \dots, A_{N} のうち、 A_{i} を除いて大きい順に  M 個を考える。

それらの値を  A_{i} + x + 1 以上にするのに必要な得票数を  S とする。

このとき、 S \gt R - x であるかどうかを判定せよ。


あとは、 S さえ求められればよい。これは lower_bound() や累積和を用いて高速に求められる。

全体として、計算量は  O(N \log K \log N) となる。

コード

#include <bits/stdc++.h>
using namespace std;
using pll = pair<long long, long long>;

int main() {
    long long N, M, K;
    cin >> N >> M >> K;
    vector<long long> A(N), S(N+1, 0);
    vector<pll> pA(N);
    for (int i = 0; i < N; i++) cin >> A[i], pA[i] = pll(A[i], i);
    sort(A.begin(), A.end()), sort(pA.begin(), pA.end());
    for (int i = 0; i < N; i++) S[i+1] = S[i] + A[i];
    long long rem = K - S.back();  // 残り票数

    // 例外処理
    if (M == N) {
        for (int i = 0; i < N; i++) cout << 0 << " ";
        cout << endl;
        return 0;
    }

    vector<long long> res(N);
    for (int i = 0; i < N; i++) {
        int person = pA[i].second;
        long long low = -1, high = rem+1;
        while (high - low > 1) {
            long long x = (low + high) / 2;

            // 上位 num 人の得票数を A[i] + x + 1 以上にするのに必要な票数 sum を求める
            long long sum = 0, border = A[i] + x + 1;
            long long mid = lower_bound(A.begin(), A.end(), border) - A.begin();

            // もし上位 M 人の中に人 person が含まれる場合は M+1 人に広げる
            long long num = M, sub = 0;
            if (i >= N - M) num++, sub = border - A[i];
            long long left = N - num;
            if (mid >= left) sum = border * (mid - left) - (S[mid] - S[left]) - sub;
            if (sum <= rem - x) low = x;
            else high = x;
        }
        res[person] = (high <= rem ? high : -1);
    }
    for (auto val : res) cout << val << " ";
    cout << endl;
}