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

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

AtCoder ABC 318 F - Octopus (黄色, 575 点)

高速な解法もあるっぽいけど、愚直に解いた。

問題概要

一直線上に  N 個の宝が座標  x_{1}, x_{2}, \dots, x_{N} に配置されている。一方、長さが  L_{1}, L_{2}, \dots, L_{N} である  N 本の棒がある。次の条件を満たす整数  x の個数を求めよ。


 i = 1, 2, \dots, N に対して、座標  k の点を始点とするように棒を置いたとき、棒が宝を含むとき、それらの宝を選んで取り除いていくことを繰り返したとき、宝をすべて取り除くことができる。


制約

  •  1 \le N \le 200

考えたこと

この手の問題では、ギリギリの点を列挙するのが定石だと思う。そうした点と点の間にある座標については、「常に条件を満たす」「常に条件を満たさない」のいずれかになる感じになる。

ここでは、各  i, j に対して、 x_{i} \pm L_{j} らが候補点となる。つまり、候補点は  O(N^{2}) 個ある。それぞれの候補点、および間の点たちについて、 Greedy で  O(N \log N) で判定できる。

よって全体として  O(N^{3} \log N) の計算量となる。

高速な解法

コード

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

int main() {
    int N;
    cin >> N;
    vector<long long> X(N), L(N);
    for (int i = 0; i < N; ++i) cin >> X[i];
    for (int i = 0; i < N; ++i) cin >> L[i];
    
    vector<long long> alt;
    for (int i = 0; i < N; ++i) {
        for (int j = 0; j < N; ++j) {
            alt.push_back(X[i] - L[j]);
            alt.push_back(X[i] + L[j]);
        }
    }
    const long long INF = 1LL<<62;
    alt.push_back(-INF);
    alt.push_back(INF);
    sort(alt.begin(), alt.end());
    alt.erase(unique(alt.begin(), alt.end()), alt.end());
    
    auto check = [&](long long x) -> bool {
        vector<long long> dis;
        for (int i = 0; i < N; ++i) dis.push_back(abs(x - X[i]));
        sort(dis.begin(), dis.end());
        for (int i = 0; i < N; ++i) if (dis[i] > L[i]) return false;
        return true;
    };

    long long res = 0;
    for (int i = 0; i+1 < alt.size(); ++i) {
        if (check(alt[i])) ++res;
        if (check((alt[i] + alt[i+1])/2)) res += alt[i+1] - alt[i] - 1;
    }
    cout << res << endl;
}