【做題】POI2011R1 - Plot——最小圓覆蓋&倍增
阿新 • • 發佈:2018-12-30
增加 key void str ref 時間 cnblogs 答案 思路
的區間,我們要做\(\log l\)最小圓覆蓋,每次要處理的點的個數都是不超過\(2l\)的。因此,這個區間的復雜度就是\(O(l \log l)\),總復雜度為\(O(n \log^2 n)\),可以通過本題。
原文鏈接 https://www.cnblogs.com/cly-none/p/loj2159.html
題意:給出\(n\)個點,你需要按編號將其劃分成不超過\(m\)段連續的區間,使得所有每個區間的最小圓覆蓋的半徑的最大值最小。輸出這個最小化的最大值和方案(圓心)。
\(n,m \leq 10^5\)
顯然要二分答案。然後,一個區間就是越長越好。
首先,考慮最小圓覆蓋\(O(n)\)的隨機增量法,但為了保證復雜度,我們需要能夠random_shuffle
。如果直接按照編號順序添加點,時間復雜度會是\(O(n^3 \log n)\)的。
為了能夠random_shuffle
,我們不能一個個添加結點,而是在一開始就知道要對哪些點求最小圓覆蓋。
一個思路是二分區間長度。因為有\(m\)段區間,所以這麽做是\(O(nm \log^2 n)\)的。並不優秀,在數據水的情況下還不如上一種做法。
為何二分的復雜度不優秀?在於它沒有利用所有區間長度和是\(n\)這一性質,也就是二分的上界太大了。
於是我們可以考慮增加區間長度。一個套路是分塊。不斷增加\(\sqrt n\)的長度,到不行時再改為增加\(1\)的長度。這樣在這個區間長度為\(l\)的情況下,復雜度是\(O(l \sqrt l)\)的。於是就得到了\(O(n \sqrt n \log n)\)的做法。
然而實際上倍增就好了。分為兩步,首先,我們找到答案二進制下最高的一位,然後,向下確定每一位。這樣,對於一個長度為\(l\)
#include <bits/stdc++.h> using namespace std; #define gc() getchar() template <typename tp> inline void read(tp& x) { x = 0; char tmp; bool key = 0; for (tmp = gc() ; !isdigit(tmp) ; tmp = gc()) key = (tmp == '-'); for ( ; isdigit(tmp) ; tmp = gc()) x = (x << 3) + (x << 1) + (tmp ^ '0'); if (key) x = -x; } typedef double db; const db eps = 1e-8; inline int jud(db x) { return x > -eps ? x > eps ? 1 : 0 : -1; } struct point { db x,y; point(db x_=0, db y_=0): x(x_), y(y_) {} point operator + (const point& a) const { return point(x + a.x, y + a.y); } point operator - (const point& a) const { return point(x - a.x, y - a.y); } db abs() const { return sqrt(x * x + y * y); } db norm() const { return x * x + y * y; } point perp() const { return point(- y, x); } point operator * (const db& a) const { return point(x * a, y * a); } }; db cross(point a,point b) { return a.x * b.y - a.y * b.x; } db dot(point a,point b) { return a.x * b.x + a.y * b.y; } point unit(point a) { db d = a.abs(); a.x /= d; a.y /= d; return a; } struct line { point u,v; line(point u_=point(), point v_=point()): u(u_) { v = unit(v_); } }; db dis(point a,line b) { return cross(a - b.u, b.v); } point inse(line a,line b) { assert(jud(cross(a.v, b.v)) != 0); db d = dis(a.u, b) / cross(a.v, b.v); return a.u - (a.v * d); } struct circle { point o; db r; circle(point o_=point(), db r_ = 0): o(o_), r(r_) {} }; circle circum(point a,point b,point c) { line l1 = line((a + b) * (0.5), (a - b).perp()); line l2 = line((b + c) * (0.5), (b - c).perp()); point d = inse(l1,l2); return circle(d, (a - d).abs()); } const int N = 100010; int n,m,cnt,num; point po[N], ans[N], tmp[N]; bool check(int l,int r,db x) { for (int i = l ; i <= r ; ++ i) tmp[i] = po[i]; random_shuffle(tmp+l,tmp+r+1); circle cir = circle(tmp[l], 0); for (int i = l+1 ; i <= r ; ++ i) { if (jud(cir.r - (tmp[i] - cir.o).abs()) >= 0); else { cir = circle(tmp[i], 0); for (int j = l ; j < i ; ++ j) { if (jud(cir.r - (tmp[j] - cir.o).abs()) >= 0); else { cir = circle((tmp[i] + tmp[j]) * (0.5), (tmp[i] - tmp[j]).abs() * 0.5); for (int k = l ; k < j ; ++ k) { if (jud(cir.r - (tmp[k] - cir.o).abs()) >= 0); else cir = circum(tmp[i], tmp[j], tmp[k]); } } } } } ans[cnt] = cir.o; return jud(x - cir.r) >= 0; } bool doit(db x) { cnt = 1; for (int lp = 1, len ; lp <= n ; lp += len, ++ cnt) { for (len = 1 ; lp + (len<<1) - 1 <= n && check(lp, lp + (len << 1) - 1, x) ; len <<= 1); for (int i = (len >> 1) ; i >= 1 ; i >>= 1) if (lp + len + i - 1 <= n && check(lp, lp + len + i - 1, x)) len += i; check(lp, lp + len - 1, x); } -- cnt; return cnt <= m; } int main() { read(n), read(m); for (int i = 1, x, y ; i <= n ; ++ i) { read(x), read(y); po[i] = point(x, y); } db l = 0, r = 2000000, mid; while (r - l > 1e-8) { mid = (l + r) / 2.0; if (doit(mid)) r = mid; else l = mid; } printf("%.7lf\n",r); doit(r); printf("%d\n",cnt); for (int i = 1 ; i <= cnt ; ++ i) printf("%.7lf %.7lf\n", ans[i].x, ans[i].y); return 0; }
小結:本題反應了倍增的特性。也就是每次需要判斷的大小和答案大小是同一級別的。因此,在\(O(ANS)\)判斷一個答案的合法性時,可以用倍增替代二分來保證復雜度。
【做題】POI2011R1 - Plot——最小圓覆蓋&倍增