[HNOI2007]最小矩形覆盖(旋转卡壳)

题意

给定一些点的坐标,要求求能够覆盖所有点的最小面积的矩形,输出所求矩形的面积和四个顶点坐标

题解

先求得一个凸包

可以知道对于凸包每一条边,其对应一个最小矩形覆盖,而凸包整体最小矩形覆盖在这N个解之中

对于凸包一条边CH[i]->CH[i+1],利用旋转卡壳求得其对踵点j

于是jCH[i]->CH[i+1]方向共同确定了矩形的高h以及长、高向量\vec{L},\vec{H}

这里我们取\vec{H}指向凸包内部,则\vec{L}CH[i]->CH[i+1]CH[i+1]->CH[i](逆时针转90°为\vec{H}),且二者为单位向量

现在求\vec{L}方向的最远点(k正,l负),这里利用旋转卡壳求有向三角形面积即可

值得注意的是由于凸包封闭,k,l是单峰的,但不是单调的,故要考虑向前走或向后走+1,-1

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;

const int MAXN = 1e6 + 5;
const double PI = acos(-1.0);
const double epsilon = 1e-6;
const double INF = 1e30;
int T, N, M, K;
struct Point {
  double x, y;
  Point(double x0 = 0, double y0 = 0) { x = x0, y = y0; }
  Point operator+(Point p2) { return Point(x + p2.x, y + p2.y); }
  Point operator-(Point p2) { return Point(x - p2.x, y - p2.y); }
  Point operator*(double b) { return Point(x * b, y * b); }
  Point operator/(double b) { return Point(x / b, y / b); }
  double operator*(Point p2) { return x * p2.x + y * p2.y; }
  double operator^(Point p2) { return x * p2.y - y * p2.x; }
  Point rotate(double a) {
    return Point(x * cos(a) - y * sin(a), x * sin(a) + y * cos(a));
  }
  double norm2(Point p2) {
    return (x - p2.x) * (x - p2.x) + (y - p2.y) * (y - p2.y);
  }
  double dis(Point p2) { return sqrt(norm2(p2)); }
} p[MAXN], CH[MAXN], ans[4];
typedef Point Vector;
bool cmp_CH(Point p1, Point p2);
int sck[MAXN], cnt_sck;
vector<Point> V;

int main() {
  //
  scanf("%d", &N);
  for (int i = 0; i < N; i++) scanf("%lf%lf", &p[i].x, &p[i].y);
  sort(p, p + N, cmp_CH);
  sck[++cnt_sck] = 0, sck[++cnt_sck] = 1;
  for (int i = 2; i < N; i++) {
    while (cnt_sck > 1 && ((p[sck[cnt_sck]] - p[sck[cnt_sck - 1]]) ^
                           (p[i] - p[sck[cnt_sck - 1]])) <= 0)
      cnt_sck--;
    sck[++cnt_sck] = i;
  }
  int tmp = cnt_sck;
  for (int i = N - 2; i >= 0; i--) {
    while (cnt_sck > tmp && ((p[sck[cnt_sck]] - p[sck[cnt_sck - 1]]) ^
                             (p[i] - p[sck[cnt_sck - 1]])) <= 0)
      cnt_sck--;
    sck[++cnt_sck] = i;
  }
  M = cnt_sck - 1;
  for (int i = 0; i <= M; i++) CH[i] = p[sck[i + 1]];
  // for (int i = 0; i <= M; i++) printf("%lf %lf\n", CH[i].x, CH[i].y);
  double minn = INF;
  for (int i = 0, j = 1, k = 1, l = 1; i < M; i++) {
    while (((CH[i + 1] - CH[i]) ^ (CH[j] - CH[i])) <=
           ((CH[i + 1] - CH[i]) ^ (CH[j + 1] - CH[i])))
      j = (j + 1) % M;
    Vector H, L;
    L = (CH[i] - CH[i + 1]) / CH[i].dis(CH[i + 1]);
    H = L.rotate(PI / 2);
    while (((CH[k] - CH[j]) ^ H) <= ((CH[k + 1] - CH[j]) ^ H)) k = (k + 1) % M;
    while (((CH[k] - CH[j]) ^ H) <= ((CH[(k - 1 + M) % M] - CH[j]) ^ H))
      k = (k - 1 + M) % M;
    while (((CH[l] - CH[j]) ^ H) >= ((CH[l + 1] - CH[j]) ^ H)) l = (l + 1) % M;
    while (((CH[l] - CH[j]) ^ H) >= ((CH[(l - 1 + M) % M] - CH[j]) ^ H))
      l = (l - 1 + M) % M;
    double l1, l2, h;
    l1 = (CH[k] - CH[j]) ^ H, l2 = (CH[l] - CH[j]) ^ H;
    h = ((CH[i + 1] - CH[i]) ^ (CH[j] - CH[i])) / CH[i].dis(CH[i + 1]);
    if (fabs(l1 - l2) * h < minn) {
      minn = fabs(l1 - l2) * h;
      // cout << CH[i].x << " " << CH[i].y << endl;
      // cout << CH[j].x << " " << CH[j].y << endl;
      // cout << CH[k].x << " " << CH[k].y << endl;
      // cout << CH[l].x << " " << CH[l].y << endl;
      // cout << l1 << " " << l2 << endl;
      ans[0] = CH[j] + L * l1, ans[3] = CH[j] + L * l2;
      ans[1] = ans[0] + H * h, ans[2] = ans[3] + H * h;
      // cout << endl;
    }
  }
  int id = 0;
  for (int i = 1; i < 4; i++)
    if (ans[i].y < ans[id].y || (ans[i].y == ans[id].y && ans[i].x < ans[id].x))
      id = i;
  printf("%.5lf\n", minn + epsilon);
  for (int i = 0; i < 4; i++) {
    printf("%.5lf %.5lf\n", ans[id].x + epsilon, ans[id].y + epsilon);
    id = (id + 1) % 4;
  }
  return 0;
}

bool cmp_CH(Point p1, Point p2) {
  if (p1.y == p2.y) return p1.x < p2.x;
  return p1.y < p2.y;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值