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

题目大意:给出若干点,求最小矩形,使得矩形能够覆盖所有点(输出面积、逆时针输出四个顶点的坐标)。

思路:显然这个矩形要覆盖它们的凸包。

            一个结论:求出这些点的凸包后,最小矩形至少有一条边贴着凸包(感觉很对,不知道严谨的证明qwq)。

           然后就很容易了,只要枚举哪一条边(i,i+1)作为矩形的一边,然后分别求出对边和邻边卡过哪三个点就可以了。

            这个可以用指针指向三个合法点,然后随着i的逆时针移动,这三个合法点一定也顺次移动,所以总移动次数是线性的,不会超时。

            假设当前边为(A,B)

           求对边经过的点C的方法: 根据向量|AC×AB|先增大后减小的规律(可以理解为三角形底不变,高最大时面积最大),可以找到最高点C   

            求左邻边的点D的方法:根据向量|AD·AB| 在左端点最大的性质(可以理解为左端点在AB上投影最长),可以找到左端点。

            右端点方法类似。

              这题注意几个坑点:1.把重复的点去掉;

                                             2.输出可能会有-0.00000的情况,所以需要改成0.00000

扫描二维码关注公众号,回复: 4442540 查看本文章

                                              3.左端点和右端点除了考虑点积以外还要考虑它们是在左边还是右边,所以点积的正负也需要考虑。

#include <cstdio>
#include <cmath>
#include <algorithm>
#define db double
#define eps 1e-9
#define rep(i,j,k) for (i=j;i<=k;i++)
#define down(i,j,k) for (i=j;i>=k;i--)
using namespace std;
const int N=5e4+5;
int n,i,wh,flg;
db ans,tmp;
struct pt{
	db x,y;
	pt (db X=0,db Y=0) {x=X; y=Y;}
	db operator ^ (const pt &B) { return x*B.x+y*B.y; }
	db operator * (const pt &B) { return x*B.y-B.x*y; }
	pt operator + (const pt &B) { return pt(x+B.x,y+B.y); }
	pt operator - (const pt &B) { return pt(x-B.x,y-B.y); }
	pt operator * (const db &c) { return pt(x*c,y*c);     }
}E,a[N],c[N*2],stk[N],xtk[N],v[10],w[10]; int stp,xtp,cn;
struct seg{
	pt s,e,v;
	seg (pt S=pt(0,0),pt E=pt(0,0)) {s=S; e=E; v=E-S;}
}L,R,U,B;
bool cmp(const pt &A,const pt &B)
{
	if (fabs(A.x-B.x)>eps) return A.x<B.x; 
	return A.y<B.y;
}
void Convex()
{
	int tn;
	sort(a+1,a+1+n,cmp);
	tn=n; n=1;
	rep(i,2,tn)
		if (a[i].x!=a[i-1].x || a[i].y!=a[i-1].y) a[++n]=a[i]; //去除重复的点
	stp=0;
	rep(i,1,n) {
		while (stp>1 && (a[i]-stk[stp])*(stk[stp]-stk[stp-1])<-eps) stp--;
		stk[++stp]=a[i];
	}
	xtp=0;
	rep(i,1,n) {
		while (xtp>1 && (a[i]-xtk[xtp])*(xtk[xtp]-xtk[xtp-1])>eps) xtp--;
		xtk[++xtp]=a[i];
	}
	cn=0;
	rep(i,1,stp) c[++cn]=stk[i];
	down(i,xtp-1,2) c[++cn]=xtk[i];
	reverse(c+1,c+1+cn);
}
db S(pt A,pt B,pt C) { return fabs((A-B)*C);}
db D(pt A,pt B,pt C) { return (A-B)^C;}
pt crs(seg A,seg B) {
	db S1=(B.s-A.s)*A.v,S2=A.v*B.v;
	return B.s+(B.v*(S1/S2));
}
int sgn(db a,db b)
{
	if (a-b<-eps) return -1;
	if (fabs(a-b)<eps) return 0;
	return 1;
}
void solve()
{
	int l=3,r=2,mid=3,j;
	rep(i,1,cn) c[cn+i]=c[i]; //复制一份,写起来不要考虑环的问题,方便一点
	rep(i,1,cn)
	{
		E=c[i+1]-c[i]; //当前边的向量
		for (;mid<cn*2 && S(c[mid+1],c[i],E)>S(c[mid],c[i],E);mid++);
		for (;(l<cn+i) && (  (-D(c[l+1],c[i],E)>-D(c[l],c[i],E) ) || (D(c[l],c[i],E)>0) );l++); //尤其注意左端点落到了右方的情况,这时左端点需要继续逆时针转动直到落到最左端
		for (;(r<mid) && (D(c[r+1],c[i],E)>D(c[r],c[i],E));r++);
		L=seg(c[l],c[l]+pt(E.y,-E.x)); //求出四条边的向量
		R=seg(c[r],c[r]+pt(E.y,-E.x));
		U=seg(c[mid],c[mid]+pt(E.x,E.y));
		B=seg(c[i],c[i+1]);
		v[0]=crs(B,R); v[1]=crs(R,U); v[2]=crs(U,L); v[3]=crs(L,B);  //求四个交点
		tmp=fabs((v[2]-v[1])*(v[1]-v[0]));
		if (flg==0 || tmp<ans) //更新答案
		{
			ans=tmp; flg=1;
			rep(j,0,3) w[j]=v[j];
		}
	}
}
db spc(db x)
{
	if (fabs(x)<eps) return 0.0;
	return x;
}
int main()
{
	#ifdef ONLINE_JUDGE
	#else
		freopen("rectangle.in","r",stdin);
		freopen("rectangle.out","w",stdout);
	#endif
	scanf("%d",&n);
	rep(i,1,n) scanf("%lf%lf",&a[i].x,&a[i].y);
	Convex(); //求凸包
	solve(); //算答案
	rep(i,0,3) w[4+i]=w[i];
	wh=0; rep(i,1,3)
	if ((sgn(w[i].y,w[wh].y)==-1) || (sgn(w[i].y,w[wh].y)==0 && sgn(w[i].x,w[wh].x)==-1)) wh=i;
	printf("%.5lf\n",spc(ans));
	rep(i,0,3) printf("%.5lf %.5lf\n",spc(w[wh+i].x),spc(w[wh+i].y));
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Orzage/article/details/80714901