1. 程式人生 > >【洛谷 P3187】 [HNOI2007]最小矩形覆蓋 (二維凸包,旋轉卡殼)

【洛谷 P3187】 [HNOI2007]最小矩形覆蓋 (二維凸包,旋轉卡殼)

ref scanf const 維護 math int() 一個 數據 pre

題目鏈接
嗯,毒瘤題。
首先有一個結論,就是最小矩形一定有條邊和凸包重合。腦補一下就好了。
然後枚舉凸包的邊,用旋轉卡殼維護上頂點、左端點、右端點就好了。
上頂點用叉積,叉積越大三角形面積越大,對應的高也就越大。兩邊的點用點積,點積越大投影越大。
然後就是精度問題。這種實數計算最好不要直接用比較運算符,要用差和\(eps\)的關系來比較,我就是一直卡在這裏。還好有爆炸\(OJ\)離線題庫提供的數據。。。

#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
const int MAXN = 50010;
const double eps = 1e-8;
struct point{
    double x, y;
    inline double dis(){
        return sqrt(x * x + y * y);
    }
    inline void print(){
        if(fabs(x) < 1e-10) x = 0;
        if(fabs(y) < 1e-10) y = 0;
        printf("%.5lf %.5lf\n", x, y);
    }
}p[MAXN];
inline double sig(double x){
    return (x > eps) - (x < -eps);
}
int operator == (point a, point b){
    return a.x == b.x && a.y == b.y;
}
point operator * (point a, double b){    // ba
    return (point){ a.x * b, a.y * b };
}
double operator * (point a, point b){    // a x b
    return a.x * b.y - b.x * a.y;
}
double operator / (point a, point b){    // a . b
    return a.x * b.x + a.y * b.y;
}
point operator - (point a, point b){     // a - b
    return (point){ a.x - b.x, a.y - b.y };
}
point operator + (point a, point b){     // a + b
    return (point){ a.x + b.x, a.y + b.y };
}
int cmp(const point a, const point b){   
    return a.x == b.x ? a.y < b.y : a.x < b.x;
}
inline int judge(point a, point b, point c){    //Kab > Kac
    return (b.y - a.y) * (c.x - a.x) > (c.y - a.y) * (b.x - a.x); 
}
inline double mult(point a, point b, point c){
    return (a - c) * (b - c);
}
inline double calc(point a, point b, point c){
    return (b - a) / (c - a);
}
int n, top, tp;
point st[MAXN], ts[MAXN], Ans[5];
double ans = 1e18, d, a, b, L, R;
int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
       scanf("%lf%lf", &p[i].x, &p[i].y);
    sort(p + 1, p + n + 1, cmp);
    for(int i = 1; i <= n; ++i){
       if(p[i] == p[i - 1]) continue;
       while(top > 1 && judge(st[top - 1], st[top], p[i])) --top;
       st[++top] = p[i];
    }
    for(int i = 1; i <= n; ++i){
       if(p[i] == p[i - 1]) continue;
       while(tp > 1 && !judge(ts[tp - 1], ts[tp], p[i])) --tp;
       ts[++tp] = p[i];
    }
    for(int i = tp - 1; i; --i) st[++top] = ts[i];
    --top;
    int j = 2, k = 2, l = 2;
    for(int i = 1; i <= top; ++i){
        while(sig(mult(st[i], st[i + 1], st[j]) - mult(st[i], st[i + 1], st[j + 1])) <= 0) if(++j > top) j = 1;
        while(sig(calc(st[i], st[i + 1], st[k]) - calc(st[i], st[i + 1], st[k + 1])) <= 0) if(++k > top) k = 1;
        if(i == 1) l = k;
        while(sig(calc(st[i], st[i + 1], st[l]) - calc(st[i], st[i + 1], st[l + 1])) >= 0) if(++l > top) l = 1;
        d = (st[i] - st[i + 1]).dis();
        R = calc(st[i], st[i + 1], st[k]) / d;
        L = calc(st[i], st[i + 1], st[l]) / d;
        b = fabs(mult(st[i], st[i + 1], st[j]) / d);
        a = R - L;
        if(a * b < ans){
           ans = a * b;
           Ans[1] = st[i] + (st[i + 1] - st[i]) * (R / d);
           Ans[2] = Ans[1] + (st[k] - Ans[1]) * (b / (st[k] - Ans[1]).dis());
           Ans[3] = Ans[2] + (st[i] - Ans[1]) * (a / R);
           Ans[4] = Ans[3] + (Ans[1] - Ans[2]);
        }
    }
    printf("%.5lf\n", ans);
    double Min = 1e18, pos;
    for(int i = 1; i <= 4; ++i)
       if(Ans[i].y < Min)
         Min = Ans[i].y, pos = i;
    for(int i = pos; i <= 4; ++i)
       Ans[i].print();
    for(int i = 1; i < pos; ++i)
       Ans[i].print();
    return 0;
}

【洛谷 P3187】 [HNOI2007]最小矩形覆蓋 (二維凸包,旋轉卡殼)