最小包含円と、マッチングの二部構成問題
問題概要
個の円 ( と番号付けされている) と、 個の多角形 ( と番号付けされている) とが与えられる。
- 各円は、中心の座標と半径
- 各多角形は、各頂点の座標
が与えられる。すべての多角形が、いずれかの円にしっかりと収まるようにしたい。そのようなことが可能かどうか判定し、可能ならば (多角形 1 が入る円番号, 多角形 2 が入る円番号, ..., 多角形 が入る円番号) が辞書順最小になるものを求めよ。
制約
考えたこと
公式解説はここ
http://rippro.org/event/acpc2012/rippro.org
この問題は次の二部パートにわかれる
- 多角形 が円 に収まるかどうかを判定
- 辞書順最小な二部マッチングを求める
前半は、多角形の最小包含円を求めれば OK。最小包含円を求める問題は、AtCoder でそのまま出た!ここでは、三分探索でやってみた。
二部マッチングパートは、辞書順という要求がなかったら、Greedy にできる。辞書順最小という要求は間に合うならば
- 1 個目の多角形を円 1 に割り当てても最大マッチングを達成できるか判定し、できなかったら円 2 を...
という風にやっていけば OK。
#include <iostream> #include <iomanip> #include <vector> #include <cmath> #include <algorithm> using namespace std; using DD = double; const DD INF = 1LL<<60; // to be set appropriately const DD EPS = 1e-6; // to be set appropriately const DD PI = acosl(-1.0); DD torad(int deg) {return (DD)(deg) * PI / 180;} DD todeg(DD ang) {return ang * 180 / PI;} /* Point */ struct Point { DD x, y; Point(DD x = 0.0, DD y = 0.0) : x(x), y(y) {} friend ostream& operator << (ostream &s, const Point &p) {return s << '(' << p.x << ", " << p.y << ')';} }; inline Point operator + (const Point &p, const Point &q) {return Point(p.x + q.x, p.y + q.y);} inline Point operator - (const Point &p, const Point &q) {return Point(p.x - q.x, p.y - q.y);} inline Point operator * (const Point &p, DD a) {return Point(p.x * a, p.y * a);} inline Point operator * (DD a, const Point &p) {return Point(a * p.x, a * p.y);} inline Point operator * (const Point &p, const Point &q) {return Point(p.x * q.x - p.y * q.y, p.x * q.y + p.y * q.x);} inline Point operator / (const Point &p, DD a) {return Point(p.x / a, p.y / a);} inline Point conj(const Point &p) {return Point(p.x, -p.y);} inline Point rot(const Point &p, DD ang) {return Point(cos(ang) * p.x - sin(ang) * p.y, sin(ang) * p.x + cos(ang) * p.y);} inline Point rot90(const Point &p) {return Point(-p.y, p.x);} inline DD cross(const Point &p, const Point &q) {return p.x * q.y - p.y * q.x;} inline DD dot(const Point &p, const Point &q) {return p.x * q.x + p.y * q.y;} inline DD norm(const Point &p) {return dot(p, p);} inline DD abs(const Point &p) {return sqrt(dot(p, p));} inline DD amp(const Point &p) {DD res = atan2(p.y, p.x); if (res < 0) res += PI*2; return res;} inline bool eq(const Point &p, const Point &q) {return abs(p - q) < EPS;} inline bool operator < (const Point &p, const Point &q) {return (abs(p.x - q.x) > EPS ? p.x < q.x : p.y < q.y);} inline bool operator > (const Point &p, const Point &q) {return (abs(p.x - q.x) > EPS ? p.x > q.x : p.y > q.y);} inline Point operator / (const Point &p, const Point &q) {return p * conj(q) / norm(q);} // 最小包含円 const int ITER = 100; DD f(const vector<Point> &v, DD x, DD y) { DD res = 0; for (int i = 0; i < v.size(); ++i) res = max(res, abs(v[i] - Point(x, y))); return res; } DD g(const vector<Point> &v, DD x) { DD left = -10000, right = 10000; for (int _ = 0; _ < ITER; ++_) { DD m1 = (left * 2 + right) / 3; DD m2 = (left + right * 2) / 3; if (f(v, x, m1) > f(v, x, m2)) left = m1; else right = m2; } return f(v, x, left); } DD mic(const vector<Point> &v) { DD left = -10000, right = 10000; for (int _ = 0; _ < ITER; ++_) { DD m1 = (left * 2 + right) / 3; DD m2 = (left + right * 2) / 3; if (g(v, m1) > g(v, m2)) left = m1; else right = m2; } return g(v, left); } // 人がすべてマッチできるか bool can(vector<DD> person, vector<DD> ana) { sort(person.begin(), person.end()); sort(ana.begin(), ana.end()); int j = 0; for (int i = 0; i < person.size(); ++i) { while (j < ana.size() && person[i] > ana[j] + EPS) ++j; if (j == ana.size()) return false; ++j; } return true; } int main() { int N, M; cin >> N >> M; vector<pair<DD,int>> r(N), p(M); vector<vector<Point>> vp(M); for (int i = 0; i < N; ++i) cin >> r[i].first, r[i].second = i; for (int i = 0; i < M; ++i) { int a; cin >> a; vp[i].resize(a); for (int j = 0; j < a; ++j) cin >> vp[i][j].x >> vp[i][j].y; p[i] = {mic(vp[i]), i}; } vector<DD> person, ana; for (int i = 0; i < p.size(); ++i) person.push_back(p[i].first); for (int i = 0; i < r.size(); ++i) ana.push_back(r[i].first); if (!can(person, ana)) { cout << "NG" << endl; return 0; } for (int i = 0; i < M; ++i) { vector<DD> person; for (int j = i+1; j < M; ++j) person.push_back(p[j].first); for (int j = 0; j < r.size(); ++j) { if (p[i].first > r[j].first + EPS) continue; vector<DD> ana; for (int k = 0; k < r.size(); ++k) { if (k == j) continue; ana.push_back(r[k].first); } if (can(person, ana)) { cout << r[j].second + 1 << endl; r.erase(r.begin()+j); break; } } } }