【旋转卡壳】最小矩形覆盖

这道题目探讨的是如何找到最小矩形以覆盖所有点,其中关键在于利用凸包性质和旋转卡壳算法。通过证明最小矩形至少有一边与凸包边重合,可以先找到凸包,然后对每条边应用旋转卡壳,通过叉积和点积来确定矩形的边界。具体方法是找到离当前边最远的点、最左/右的点,以这些点和边构建矩形。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:

这里写图片描述


分析:

这道题是旋转卡壳的一个典型问题
首先我们瞎蒙一个性质:
我们目标状态的矩形一定有一条边是凸包上的边(的延长线)
其实是可以证明的:
首先,最小矩形必然每条边上都有点
那么如果不是凸包上的边:
这里写图片描述
那么我们可以通过旋转这个矩形,使得这个矩形的某条边上不再有点
这里写图片描述
所以,我们只需要找出凸包,暴力枚举每条边,用旋转卡壳来确定这个矩形。
那么,如何卡壳呢?
很简单,我们考虑叉积和点积的几何意义:
叉积:可以求出一个点与一条线所构成的三角形面积。
点积:可以求出一条向量在另一条上的投影长度。
那么根据这两点,我们可以通过叉积找到离当前线段最远的点,通过点积找到离当前线段最左/右的点,通过这三个点和这条线段,我们就可以确定这个矩形了。当然,我们需要按照顺时针/逆时针的顺序依次枚举每条边,然后再贪心地确定三个特殊点即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<cmath>
#define SF scanf
#define PF printf
#define MAXN 100010
#define EPS 1e-8
using namespace std;
struct node{
    double x,y;
    node(){}
    node(double xx,double yy):x(xx),y(yy) {}
    node operator + (const node &a) const {
        return node(x+a.x,y+a.y);
    }
    node operator - (const node &a) const {
        return node(x-a.x,y-a.y);
    }
    node operator * (const double &t) const {
        return node(x*t,y*t);
    }
    double operator *(const node &a) const{
        return x*a.x+y*a.y;
    } 
    double operator ^(const node &a) const{
        return x*a.y-y*a.x;
    }
    /*bool operator <(const node &a) const {
        return y<a.y||(fabs(y-a.y)<EPS&&x<a.x);
    }*/
    bool operator <(const node &a) const {
        return fabs(y-a.y)<EPS?x<a.x:y<a.y;
    }
}p[MAXN],l1[MAXN],t[5];
stack<node> s1,s;
double ans=1e60;
int n,cnt1;
void solve(node a1[],int &cnt){
    s.push(p[1]);
    s1.push(p[1]);
    s1.push(p[2]);
    for(int i=3;i<=n;i++){
        while(!s.empty()&&((p[i]-s.top())^(s1.top()-s.top()))<=0){
            s.pop();
            s1.pop();
        }
        s.push(s1.top());
        s1.push(p[i]);
    }
    while(!s1.empty()){
        a1[++cnt]=s1.top();
        s1.pop();
    }
    while(!s.empty())
        s.pop();
}
double len(node a){
    return sqrt(a.x*a.x+a.y*a.y);
}
bool cmp(node a,node b){
    return b<a;
}
void rc(){
    int l=1,r=1,p=1;
    double L,R,D,H;
    for(int i=0;i<cnt1;i++)
    {
        D=len(l1[i]-l1[i+1]);
        while((((l1[i+1]-l1[i])^(l1[p+1]-l1[i]))-((l1[i+1]-l1[i])^(l1[p]-l1[i]))) >-EPS)
            p=(p+1)%cnt1;
        while(((l1[i+1]-l1[i])*(l1[r+1]-l1[i]))-((l1[i+1]-l1[i])*(l1[r]-l1[i]))>-EPS)
            r=(r+1)%cnt1;
        if(i==0)
            l=r;
        while(((l1[i+1]-l1[i])*(l1[l+1]-l1[i]))-((l1[i+1]-l1[i])*(l1[l]-l1[i]))<EPS)
            l=(l+1)%cnt1;
        L=(l1[i+1]-l1[i])*(l1[l]-l1[i])/D;
        R=(l1[i+1]-l1[i])*(l1[r]-l1[i])/D;
        H=((l1[i+1]-l1[i])^(l1[p]-l1[i]))/D;
        H=fabs(H);
        double tmp=(R-L)*H;
        if(tmp<ans){
            ans=tmp;
            t[0]=l1[i]+(l1[i+1]-l1[i])*(R/D);
            t[1]=t[0]+(l1[r]-t[0])*(H/len(t[0]-l1[r]));
            t[2]=t[1]-(t[0]-l1[i])*((R-L)/len(l1[i]-t[0]));
            t[3]=t[2]-(t[1]-t[0]);
        }
    }
}
int main(){
    SF("%d",&n);
    for(int i=1;i<=n;i++)
        SF("%lf%lf",&p[i].x,&p[i].y);
    sort(p+1,p+1+n,cmp);
    solve(l1,cnt1);
    sort(p+1,p+1+n);
    cnt1--;
    solve(l1,cnt1);
    cnt1--;
    l1[0]=l1[cnt1];
    /*for(int i=0;i<=cnt1;i++)
        PF("[%.3lf %.3lf]\n",l1[i].x,l1[i].y);*/
    rc();
    PF("%.5lf\n",ans);
    int fir=0;
    for(int i=1;i<4;i++)
        if(t[i]<t[fir])
            fir=i;
    for(int i=0;i<4;i++)
        PF("%.5lf %.5lf\n",t[(i+fir)%4].x,t[(i+fir)%4].y);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值