【洛谷3187】[HNOI2007] 最小矩形覆盖(旋转卡壳)
- 给定平面上\(n\)个点,求包含所有点的最小矩形面积及顶点坐标。
- \(n\le5\times10^4\)
旋转卡壳
首先求出凸包,然后发现最小矩形肯定可以通过旋转满足与至少一条边相切,且另外三个方向上各至少有一个顶点。
于是想到去枚举相切的这条边,则另外三个方向上的顶点就应该是到这条边距离最远的和在这条边上的投影最小/最大(带符号值)的。
由于在枚举边的时候这三个点都单调移动,可以直接用双指针维护。
然后要求矩形顶点坐标,先求出投影最小/最大的两个点在这条边上的投影,然后根据最远点到这条边的距离平移过去即可。
代码:\(O(nlogn)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 50000
#define DB double
#define eps 1e-12
using namespace std;
int n;struct P
{
DB x,y;I P(Cn DB& a=0,Cn DB& b=0):x(a),y(b){}
I P operator + (Cn P& o) Cn {return P(x+o.x,y+o.y);}
I P operator - (Cn P& o) Cn {return P(x-o.x,y-o.y);}
I P operator * (Cn DB& o) Cn {return P(x*o,y*o);}
I P operator / (Cn DB& o) Cn {return P(x/o,y/o);}
I DB operator * (Cn P& o) Cn {return x*o.x+y*o.y;}
I DB operator ^ (Cn P& o) Cn {return x*o.y-y*o.x;}
I bool operator < (Cn P& o) Cn {return x!=o.x?x<o.x:y<o.y;}
I bool operator == (Cn P& o) Cn {return fabs(x-o.x)<eps&&fabs(y-o.y)<eps;}
I DB L() Cn {return sqrt(x*x+y*y);}
}p[N+5],s[N+5];
I DB PtoS(Cn P& A,Cn P& X,Cn P& Y) {return ((Y-X)^(A-X))/(Y-X).L();}//求点到直线距离
int m;I void Get()//求凸包
{
#define pd(A,B,C) (fabs((C-B)^(B-A))<eps?(A<B)==(B<C):((C-B)^(B-A))>0)
RI i;sort(p+1,p+n+1),n=unique(p+1,p+n+1)-p-1;
for(i=1;i<=n;s[++m]=p[i++]) W(m>1&&pd(s[m-1],s[m],p[i])) --m;
for(i=n-1;i;s[++m]=p[i--]) W(m>1&&pd(s[m-1],s[m],p[i])) --m;--m;
}
I void Rotate()//旋转卡壳
{
RI i,j=3,p=1,q=2;W((s[2]-s[1])*(s[p^1?p-1:m]-s[1])-(s[2]-s[1])*(s[p]-s[1])<eps) p=p^1?p-1:m;
DB ans=1e18;RI A,B,C,D;for(i=1;i<=m;++i)
{
W(((s[i+1]-s[i])^(s[j%m+1]-s[i]))-((s[i+1]-s[i])^(s[j]-s[i]))>-eps) j=j%m+1;//距离最远的
W((s[i+1]-s[i])*(s[p%m+1]-s[i])-(s[i+1]-s[i])*(s[p]-s[i])<eps) p=p%m+1;//投影最小的
W((s[i+1]-s[i])*(s[q%m+1]-s[i])-(s[i+1]-s[i])*(s[q]-s[i])>-eps) q=q%m+1;//投影最大的
if(ans<=PtoS(s[j],s[i],s[i+1])*((s[i+1]-s[i])*(s[q]-s[i])-(s[i+1]-s[i])*(s[p]-s[i]))/(s[i+1]-s[i]).L()) continue;//与已有答案比较
ans=PtoS(s[j],s[i],s[i+1])*((s[i+1]-s[i])*(s[q]-s[i])-(s[i+1]-s[i])*(s[p]-s[i]))/(s[i+1]-s[i]).L(),A=i,B=j,C=p,D=q;
}
P w[4],f=s[A+1]-s[A];f=f/f.L();P g(-f.y,f.x);
w[0]=s[A]+f*((s[A+1]-s[A])*(s[C]-s[A])/(s[A+1]-s[A]).L());//投影最小的点对应的投影点
w[1]=s[A]+f*((s[A+1]-s[A])*(s[D]-s[A])/(s[A+1]-s[A]).L());//投影最大的点对应的投影点
w[2]=w[1]+g*PtoS(s[B],s[A],s[A+1]),w[3]=w[0]+g*PtoS(s[B],s[A],s[A+1]);//平移到对面
RI x=0;for(printf("%.5lf\n",ans),i=1;i^4;++i) (fabs(w[i].y-w[x].y)<eps?w[i].x<w[x].x:w[i].y<w[x].y)&&(x=i);
for(i=0;i^4;x=(x+1)%4,++i) printf("%.5lf %.5lf\n",fabs(w[x].x)<eps?0:w[x].x,fabs(w[x].y)<eps?0:w[x].y);
}
int main()
{
RI i;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%lf%lf",&p[i].x,&p[i].y);return Get(),Rotate(),0;
}
待到再迷茫时回头望,所有脚印会发出光芒