zoukankan      html  css  js  c++  java
  • 【做题】POI2011R1

    原文链接 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)的区间,我们要做(log l)最小圆覆盖,每次要处理的点的个数都是不超过(2l)的。因此,这个区间的复杂度就是(O(l log l)),总复杂度为(O(n log^2 n)),可以通过本题。

    #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
    ",r);
      doit(r);
      printf("%d
    ",cnt);
      for (int i = 1 ; i <= cnt ; ++ i)
        printf("%.7lf %.7lf
    ", ans[i].x, ans[i].y);
      return 0;
    }
    

    小结:本题反应了倍增的特性。也就是每次需要判断的大小和答案大小是同一级别的。因此,在(O(ANS))判断一个答案的合法性时,可以用倍增替代二分来保证复杂度。

  • 相关阅读:
    ExtJs 4.0 ExtJs2.2 JavaScript
    C++中关于classview、resourceview、fileview
    BIN OBJ 区别
    数据编码
    多线程
    REST
    SQL Server 2005安装
    临时
    数据存储
    灰度直方图
  • 原文地址:https://www.cnblogs.com/cly-none/p/loj2159.html
Copyright © 2011-2022 走看看