Codeforces Round #309 (Div.1) D. Nudist Beach

解法

二分探索して BFS で解ける。
最大化したい値を例えば仮に x とおく。
最初、使える集合 S には k 頂点以外のすべての頂点を入れておく。
queue には使えなくなった頂点(初期は k 頂点)を入れる。
(隣接頂点のうち S に含まれる個数) / (次数) が x 未満となる頂点は、その後どうあがいても使えない。
したがって、queue から使えなくなった頂点を取り出して隣接頂点の次数を1つ下げ、この時 x 未満になったら、queue に追加する、ということを繰り返すだけで解ける。
まだ使える頂点が残っていればそれが答え S の候補である。

実装によるが、コーナーケースとして x の最大値が 0 の場合がある。
答えの集合が 0 なら k 個以外の頂点を全部入れるか、二分探索の下界を負の値にしておくとよい。
計算量は O(nlogm)

ソースコード

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

int main() {
    int n, m, k; cin >> n >> m >> k;
    vector<bool> used(n);
    for(int i = 0; i < k; ++i) {
        int v; cin >> v;
        used[v - 1] = true;
    }

    vector<vector<int>> g(n);
    vector<int> deg(n);
    for(int i = 0; i < m; ++i) {
        int a, b; cin >> a >> b;
        g[a - 1].push_back(b - 1);
        g[b - 1].push_back(a - 1);
        deg[a - 1]++, deg[b - 1]++;
    }

    auto check = [&] (double x) mutable {
        queue<int> que;
        auto cur = deg;
        auto erased = used;
        for(int i = 0; i < n; ++i) {
            if(erased[i]) que.push(i);
        }
        while(!que.empty()) {
            const int v = que.front(); que.pop();
            for(auto adj : g[v]) {
                if(erased[adj]) continue;
                if(--cur[adj] < x * deg[adj]) {
                    erased[adj] = true;
                    que.push(adj);
                }
            }
        }
        vector<int> res;
        for(int i = 0; i < n; ++i) {
            if(!erased[i]) res.push_back(i + 1);
        }
        return res;
    };

    vector<int> ans;
    double lb = -1, ub = 2e5;
    for(int lp = 0; lp < 60; ++lp) {
        const auto mid = (lb + ub) / 2;
        auto tans = check(mid);
        if(tans.empty()) {
            ub = mid;
        } else {
            lb = mid;
            ans = move(tans);
        }
    }

    const int r = ans.size();
    cout << r << endl;
    for(int i = 0; i < r; ++i) {
        cout << ans[i] << " \n"[i + 1 == r];
    }
}

感想

AGC 027 C と考え方が全く同じ。