2021-07-13-P3187 [HNOI2007]最小矩形覆盖(凸包,旋转卡壳,凸包最小矩形覆盖--求面积及各点)

传送门

题意:

  1. 给定一些点的坐标,要求求能够覆盖所有点的最小面积的矩形,输出所求矩形的面积和四个顶点坐标。
  2. 3 ≤ n ≤ 50000 3\le n\le 50000 3n50000
  3. 输入n及n个点
  4. 输出面积及四个点坐标(按一定顺序,不用纠结)

题解:

  1. 先求凸包,然后再旋转卡壳
  2. 旋转卡壳具体操作:枚举点,然后卡三个点(怎么卡?建议自己推)

收获:

  1. 除了熟悉了凸包和旋转卡壳之外,还学到了很多有用的新操作,比如点到直线的距离,点到直线的投影,过一点的平行线 以及线的倾斜角,atan ,atan2等。
  2. 学的新东西,都总结在这里面:2021-07-10-计算几何总结。这里面也有一点点(主要是凸包和旋转卡壳):旋转卡壳&凸包(不用一下子就学完所有)
  3. 很多东西,都能够自己推,或者手打了。虽然慢,但是它有效啊,唯一的缺点就是容易自闭(总感觉太慢了,这两天都在搞这个题,害自闭放弃调整状态了好几次,昨下午四点钟一直玩到今早emm)。
  4. 感觉凸包和旋转卡壳一点也不难,难的是熟练的打出来和找bug(计算几何=模板+找bug+找bug+找bug)

代码:

#include <bits/stdc++.h>
#define pb push_back
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
const int N = 5e4 + 5;
const double eps = 1e-8;
const double inf = 1e9;
const double pi = acos(-1.0);
int sgn(double x) {
    if (fabs(x) < eps) return 0;
    if (x < 0)
        return -1;
    else
        return 1;
}
int n;
struct Point {
    double x, y;
    Point(double x = 0, double y = 0) : x(x), y(y) {}
    // Point() {}
    // Point(double _x, double _y) { x = _x; y = _y; }
    /*
        建议用以上形式。
        但是注意,不能:
        Point(){}
        Point(double x,double y){x=x,y=y;}
        !今天的所有错误,都来源于Point(double x,double y){x=x,y=y;}这种用法。
        Point(double x=0,double y=0):x(x),y(y){}也没有问题,就是不能上面这种
    */
    void input() { scanf("%lf%lf", &x, &y); }
    bool operator<(const Point &b) const {
        if (sgn(y - b.y) == 0)
            return sgn(x - b.x) < 0;
        else
            return sgn(y - b.y) < 0;
    }
    Point operator-(const Point &b) const { return Point(x - b.x, y - b.y); }
    double operator^(const Point &b) const { return x * b.y - b.x * y; }
    double operator*(const Point &b) const { return x * b.x + y * b.y; }
    Point operator*(double k) const { return Point(x * k, y * k); }
    Point operator+(const Point &b) const { return Point(x + b.x, y + b.y); }
    bool operator==(const Point &b) const {
        return (sgn(x - b.x)) == 0 && (sgn(y - b.y) == 0);
    }
} a[N];
vector<Point> vp;
double distance(Point a, Point b) {
    return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
struct cmp {
    Point p;
    cmp() {}
    cmp(const Point &p0) { p = p0; }
    //以点p为极坐标原点进行极角排序:sort(,,p.cmp(mi));
    bool operator()(const Point &a, const Point &b) const {
        int x = sgn((a - p) ^ (b - p));
        if (x == 0)
            return sgn(distance(a, p) - distance(b, p)) < 0;
        else
            return x > 0;
    }
};
//向量ab x 向量ac
double cross(Point a, Point b, Point c) { return (b - a) ^ (c - a); }
double dot(Point a, Point b, Point c) { return (b - a) * (c - a); }
struct polygon {
    int n;
    Point p[N];
} convex;
struct Line {
    Point s, e;
    Line() {}
    Line(Point _s, Point _e) { s = _s, e = _e; }
    //点向式,这里向量可以有很多种表示。
    Line(Point p, double angle) {
        s = p;
        e = s + Point(cos(angle), sin(angle));
    }
    //有向线段(s,e)倾斜角,(-pi,pi)。直线倾斜角只需要将角度转换为(-pi/2,pi/2)即可
    double angle() {
        double an = atan2(e.y - s.y, e.x - s.x);
        // atan(double)返回值域为(-pi/2,pi/2)
        // atan2(y,x)返回值域为(-pi,pi)
        return an;
    }
};
//由a数组求凸包传给 P ———— 没想到求凸包这么简单,之前想得太难了,艹
void getconvex(Point *a, int n, polygon &convex) {
    Point mi = a[0];
    for (int i = 1; i < n; i++) mi = min(mi, a[i]);
    sort(a, a + n, cmp(mi));  //排序:求凸包的准备工作(这里不是极角排序)
    int top = 0;  // top模拟栈顶
    convex.p[top] = a[0];
    convex.p[++top] = a[1];
    for (int i = 2; i < n; i++) {
        while (top > 0 &&
               (sgn(cross(convex.p[top - 1], convex.p[top], a[i])) <= 0))
            top--;
        convex.p[++top] = a[i];
    }
    convex.n = top + 1;
}
//点到直线距离,注意加绝对值。
double dispointtoline(Point a, Line b) {
    Point s = b.s, e = b.e;
    return fabs(cross(s, e, a)) / distance(s, e);
}
//点在直线上的投影
Point progpointtoline(Point a, Line b) {
    Point s = b.s, e = b.e;
    // dot不是cross,有找了一天的错。百炼成钢
    double k = dot(s, e, a) / distance(s, e) / distance(s, e);
    return s + (e - s) * k;
}
//调用凸包的p和n
double getminrectanglecover(Point *a, int n) {
    if (n < 3) return 0.0;
    double res = inf;
    int r1 = 1, r2 = 1, r3 = 1;
    a[n] = a[0];
    for (int i = 0; i < n; i++) {
        //卡逆时针第1,2,3个点a[r1],a[r2],a[r3]
        //注意别取等
        while (sgn((a[i + 1] - a[i]) * (a[r1 + 1] - a[r1])) > 0)
            r1 = (r1 + 1) % n;
        while (sgn(cross(a[i], a[i + 1], a[r2 + 1]) -
                   cross(a[i], a[i + 1], a[r2])) >= 0)
            r2 = (r2 + 1) % n;
        if (i == 0) r3 = r2;  //绝活
        while (sgn((a[i + 1] - a[i]) * (a[r3 + 1] - a[r3])) < 0)
            r3 = (r3 + 1) % n;
        //! 1.更新
        Line li = Line(a[i], a[i + 1]);
        double area =
            dispointtoline(a[r2], li) *
            distance(progpointtoline(a[r1], li), progpointtoline(a[r3], li));
        if (sgn(area - res) < 0) {
            res = area;
            vp.clear();
            //! 3.操作点
            vp.pb(progpointtoline(a[r1], li));
            vp.pb(progpointtoline(a[r3], li));
            Line lii = Line(a[r2], li.angle());
            vp.pb(progpointtoline(a[r1], lii));
            vp.pb(progpointtoline(a[r3], lii));
            Point mi = vp[0];
            for (auto i : vp) mi = min(mi, i);
            sort(vp.begin(), vp.end(), cmp(mi));
        }
    }
    //打印
    printf("%.5lf\n", res);
    for (auto i : vp) {
        if (sgn(i.x - eps) <= 0) i.x = 0;
        if (sgn(i.y - eps) <= 0) i.y = 0;
        printf("%.5lf %.5lf\n", i.x, i.y);
    }
    //!返回值
    return res;
}
signed main() {
    cin >> n;
    for (int i = 0; i < n; i++) a[i].input();
    getconvex(a, n, convex);
    getminrectanglecover(convex.p, convex.n);
    return 0;
}
/*
input:::
6 1.0 3.00000
1 4.00000
2.0000 1
3 0.0000
3.00000 6
6.0 3.0
output:::
18.00000
3.00000 0.00000
6.00000 3.00000
3.00000 6.00000
0.00000 3.00000
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值