色んな解法が考えられそうな問題で、ドツボにハマりやすくて危険な問題だったと思う。落ち着いて整理してシンプルに考える力が問われる。実は、二分探索などは必要ない問題である。
問題概要
人の選手 がいる。選手 の出身国は であり、座標 の位置に座っている。
各選手 に対して、
- その選手 とは出身国が異なる選手のうち、
- その選手 との距離が最も近い選手について、
その距離の最小値を求めよ。
制約
考えたこと
この問題と似ている問題で、よく知られているのは次のような問題だと思われる。
つまり、一直線上に 点が与えられて、 回のクエリが投げられて、各クエリについて座標値が与えられるので、その位置から最も近い点を求めるというものである。この問題は二分探索 (lower_bound()
) をすれば解ける典型的な問題であった。
今回の問題も、この過去の問題と同様に、二分探索による解法を考えたくなる。しかし、大きく異なる点が 2 つある。
- 選手に「色」のような属性があって、同じ色の選手は除外しなければならない
- この問題は、クエリ処理問題ではない。つまり、選手のいる座標値以外の座標については考える必要がない
特に後者は見落としがちだと思う。後者に着目して、シンプルに思考する力が問われている。
選手を座標順に並べる
今回、一旦選手を座標順に並べてみよう。具体例として、選手を (座標, 出身国) と表したとき、
(10, 3), (11, 1), (15, 1), (21, 1), (29, 2), (31, 2), (41, 3)
というデータを考えてみる。このデータに対して出身国だけを抽出すると、
3, 1, 1, 1, 2, 2, 3
のようになる。このとき、もし、各要素に対して、
left[i]
← 番目よりも左の要素であって、出身国が異なるような要素の添字の最大値 (存在しない場合は -1)right[i]
← 番目よりも右の要素であって、出身国が異なるような要素の添字の最小値 (存在しない場合は N)
を求めることができれば、この問題は解ける。各要素 に対して「i
番目の要素と left[i]
番目の要素の座標値の差」と「i
番目の要素と right[i]
番目の要素の座標値の差」の最小値を答えればよい。
そして、left
と right
の値も、for
文を用いて容易に求められる。たとえば left
は次のように求められる。
vector<int> left(N, -1), right(N, N); for (int i = 1; i < N; ++i) { if (v[i].col == v[i-1].col) left[i] = left[i-1]; else left[i] = i-1; }
計算量
最初にすべての選手を座標順にソートする部分が最も計算量を要して、 の計算量となる。
その後の left
, right
を求めたり、それを用いて答えを求める部分は の計算量で実行できる。
コード
#include <bits/stdc++.h> using namespace std; void chmin(int &a, int b) { if (a > b) a = b; } // 選手の情報 (もとの添字, 国を表す数値, 座標) struct Student { int id; long long col; long long pos; Student(int i = 0, long long c = 0, long long p = 0) : id(i), col(c), pos(p) {} }; const int INF = 1LL<<30; int main() { int N; cin >> N; // 選手を座標順にソートする (選手の元の添字も記録しておく) vector<Student> v(N); for (int i = 0; i < N; ++i) { int c, x; cin >> c >> x; v[i] = Student(i, c, x); } sort(v.begin(), v.end(), [&](Student a, Student b){return a.pos < b.pos;}); // 座標順に、各生徒から見て、前後に見たときの最初の「色が違う選手の位置」を求めていく vector<int> left(N, -1), right(N, N); for (int i = 1; i < N; ++i) { if (v[i].col == v[i-1].col) left[i] = left[i-1]; else left[i] = i-1; } for (int i = N-2; i >= 0; --i) { if (v[i].col == v[i+1].col) right[i] = right[i+1]; else right[i] = i+1; } // 答えを集計する vector<int> res(N, INF); for (int i = 0; i < N; ++i) { if (right[i] < N) chmin(res[v[i].id], v[right[i]].pos - v[i].pos); if (left[i] >= 0) chmin(res[v[i].id], v[i].pos - v[left[i]].pos); } for (int i = 0; i < N; ++i) cout << res[i] << endl; }