J: Spin Doctor
問題
n人の個人情報が与えられる。i番目の人の情報は
- a_i : 覚えている円周率の桁数 0 ~ 2000000
- b_i : 髪の毛の本数 0 ~ 2000000
- c_i : 候補者Xに投票したかどうか 0/1
実数のパラメータS, Tを適切に決めて人々を (a_i * S + b_i * T) の値でソートし、その順番で c_i == 1の人がなるべく集まるようにしなさい。
より集まっている状態とは、ソートの順番でc_i == 1の最初の人と最後の人の近いということである。
その時の最初の人と最後の人の間に何人並んでいるか、最小値を求めよ。
ただし、(a_i * S + b_i * T)が同じ値になる場合は答えが大きくなるように並ぶとする。
制約
n <= 250000
解法
a, bで2次元平面にプロットすることを思いつくと、a_i * S + b_i * T は適当な直線への正射影(垂線の足)であることが分かる。
つまり、あらゆる角度の線に対して、その射影でc_i == 1 が偏る物を見つける問題である。
あらゆる角度の平行な2本の直線でc_i == 1を挟むと、その2線の間にある頂点が答えに成ることが見て取れる。
この2線を180度半周させるとc_i == 1の凸包が見えてくる。
つまり
c_i == 1: 常に凸包の内点(頂点、辺上を含む)、答えに加算
c_i == 0 かつ凸包の内点: 答えに加算
コード
#include<cassert> #include<complex> #include<cstdio> #include<iostream> #include<vector> #include<algorithm> #include<string> #include<cstring> using namespace std; typedef long long LL; typedef vector<int> VI; #define REP(i,n) for(int i=0, i##_len=(n); i<i##_len; ++i) #define EACH(i,c) for(__typeof((c).begin()) i=(c).begin(),i##_end=(c).end();i!=i##_end;++i) #define eprintf(s...) fprintf(stderr, s) template<class T> inline void amin(T &x, const T &y) { if (y<x) x=y; } template<class T> inline void amax(T &x, const T &y) { if (x<y) x=y; } typedef long double Double; const Double EPS = 1e-8; // 0 ~~ [-EPS, EPS] // const LL EPS = 0; const Double INF = 1e12; const Double PI = acos((Double)-1); typedef complex<Double> Point; const Point I(0, 1); // abs(P a) ::= sqrt(a.x^2 + a.y^2); // norm(P a) ::= a.x^2 + a.y^2; // Point polar(rho, theta=0) int sign(Double x) { if (x < -EPS) return -1; if (EPS < x) return 1; return 0; } namespace std { bool operator<(const Point &x, const Point &y) { return real(x) != real(y)? real(x) < real(y): imag(x) < imag(y); } } Double cross(const Point &x, const Point &y) { return imag(conj(x)*y); } Double dot(const Point &x, const Point &y) { return real(conj(x)*y); } Point normal(const Point &a) { return a / abs(a); } struct Line : public vector<Point> { Line(const Point &a, const Point &b) { push_back(a); push_back(b); } Point vector() const { return back() - front(); } }; typedef vector<Point> Polygon; Double area2(const vector<Point> &g) { Double r = 0; for (int i=0; i<(int)g.size(); i++) r += cross(g[i], g[(i+1)%g.size()]); return abs(r); } int ccw(Point a, Point b, Point c) { b-=a; c-=a; if (cross(b, c) > EPS) return 1; // counter clockwise if (cross(b, c) < -EPS) return -1; // clockwise if (dot(b, c) < -EPS) return 2; // c--a--b on line if (norm(b) < norm(c)) return -2; // a--b--c on line return 0; } Polygon convex_hull(vector<Point> ps, int *c=NULL) { int n = ps.size(), k = 0; sort(ps.begin(), ps.end()); vector<Point> ch(2*n); for (int i=0; i<n; ch[k++] = ps[i++]) // lower -hull while (k >= 2 && ccw(ch[k-2], ch[k-1], ps[i]) != 1) --k; if (c) *c = k-1; for (int i = n-2, t = k+1; i >= 0; ch[k++] = ps[i--]) // upper -hull while (k >= t && ccw(ch[k-2], ch[k-1], ps[i]) != 1) --k; ch.resize(k-1); return ch; } int N; Polygon T, F; int main() { scanf("%d", &N); REP (i, N) { int x, y, c; scanf("%d%d%d", &x, &y, &c); Point p(x, y); p *= polar(1.0, 1.0); if (c) T.push_back(p); else F.push_back(p); } N = T.size(); if (N == 1) { puts("1"); return 0; } int inner = 0; int R; T = convex_hull(T, &R); { // outer points Polygon v; EACH (e, F) { if (T[0].real() <= e->real() && e->real() <= T[R].real()) { // lower int lo, hi; lo = 0, hi = R; while (hi - lo > 1) { int m = (lo + hi) / 2; if (T[m].real() <= e->real()) lo = m; else hi = m; } if (ccw(T[lo], T[lo+1], *e) == -1) { v.push_back(*e); continue; } lo = R, hi = T.size(); while (hi - lo > 1) { int m = (lo + hi) / 2; if (T[m].real() >= e->real()) lo = m; else hi = m; } if (ccw(T[lo], T[(lo+1)%T.size()], *e) == -1) { v.push_back(*e); continue; } inner++; } else { v.push_back(*e); } } F = v; } eprintf("%d %d %d\n", (int)T.size(), (int)F.size(), inner); int ans = N + inner + F.size(); { // angle vector<pair<Double, int> > G; EACH (e, F) { const Point &p = *e; int k = rand(); for (int D=1<<21; D; D>>=1) { if (D > 1 && D * 2 > (int)T.size()) continue; int b = (k - 3 * D + (int)T.size() * 10) % T.size(); k = b; assert(b >= 0); REP ($, 6) { if (ccw(p, T[b], T[(b+D)%T.size()]) != 1 && ccw(p, T[(b+D)%T.size()], T[(b+D+D)%T.size()]) != -1) k = (b+D) % T.size(); b = (b + D) % T.size(); } } Double A = arg(T[k%T.size()] - p); k = rand(); for (int D=1<<21; D; D>>=1) { if (D > 1 && D * 2 > (int)T.size()) continue; int b = (k - 3 * D + (int)T.size() * 10) % T.size(); k = b; assert(b >= 0); REP ($, 6) { if (ccw(p, T[b], T[(b+D)%T.size()]) != -1 && ccw(p, T[(b+D)%T.size()], T[(b+D+D)%T.size()]) != 1) k = (b+D) % T.size(); b = (b + D) % T.size(); } } Double B = arg(T[k%T.size()] - p); while (B + EPS < A) B += 2 * PI; assert(A <= B + EPS); assert(B <= A + PI + EPS); A -= EPS; B += EPS; G.push_back(make_pair(B, +1)); G.push_back(make_pair(A+PI, -1)); } int len = G.size(); REP (r, len) { pair<Double, int> p = G[r]; G.push_back(make_pair(p.first + 1 * PI, p.second)); G.push_back(make_pair(p.first + 2 * PI, p.second)); G.push_back(make_pair(p.first + 3 * PI, p.second)); } sort(G.begin(), G.end()); int cur = 0; EACH (e, G) { cur += e->second; assert(cur >= 0); amin(ans, N + inner + (int)F.size() - cur); } } printf("%d\n", ans); return 0; }
日記
自分にも解けるマシな幾何。 Gと解法が被ってるのは残念。
凸包が見えると解法も自然と分かる。
図とか書きたかったけどそんな難しいわけでもないしいいか。
凸包への接線求めるの難しかったけど、二分探索で良かったか。