【计算几何+巧妙旋转】 bzoj 3707

【题目大意】给定n个点,求这n个点围出来的多边形【边数>=3】的最小面积。【如果有三点共线,面积可以为0】

【思路】如果围成的形状多于3边,我们一定可以把它分成若干个三角形。所以如果要最小就必须是三角形。

我先考虑暴力做法:分别枚举3个顶点,O(n^3)。【10分】

然鹅我再思考一下:如果确定了三角形的一条边,我们可以将整个坐标系旋转一下,使这条边成为新的y轴,这时候我们只要找到离这个“y轴”最近的一个点就行了。

【到这我就不会了】

到网上搜一搜,我们发现:可以先预处理一下每两个点之间的直线,记录一下起点终点和斜率。

首先所有点按x从小到大排序。然后直线按斜率从小到大排个序。

当我们处理到直线AB时,AB就成为了y轴,且A,B是相邻的(它们距离y轴距离都为0)(假设A位置在B的上面),离AB最近的点就是AB两侧最近的点。然后到下一条直线时,由于斜率从小到大,所以是把整个图顺时针旋转了一点点,直到下一条直线成为y轴。那么这个时候把A,B在序列中的位置交换一下就行了。因为如果有别的点对相对顺序改变,那么这个点对的斜率一定介于这两条直线之间。下面口胡一下。

【这里的排位是指:在当前的“y轴”下,横坐标从小到大排序所对应的排名】


假设现在我们以l1为y轴。有一组点对,P和Q,它们相对于l1的横坐标分别是a和b,其中a<b。

那么我们一定可以保证PQ的斜率比l1小,因为l1要逆时针旋转一点点才与PQ平行。【相对原坐标系】

然后我们看到比l1斜率更小的第一条直线l2。首先,可以保证,A和B一定在直线l2的同侧。因为如果A和B在l2的异侧,那ABCD这四个点一定可以生成一条斜率介于l1和l2之间的直线【画一画就知道了】,而我们是把斜率排了序,保证了不会有这种情况出现。现在,将整个图再顺时针旋转一点点,使l2成为新的y轴。那么可以保证,A和B的相对顺序一定是改变了的,而且是把A和B的排位交换了一下。【可以脑补一下AB在CD下侧的情况,是同理的】这时候,我们看到PQ,在新的y轴下,P的横坐标是c,Q的横坐标是d,假设c>d,那么PQ的斜率一定比l2的斜率要大,因为PQ逆时针旋转一点点就与l2平行。

对于旋转过后排位会变化的点对【除AB外】,一定会满足PQ的条件:a<b且c>d。但是我们发现它的斜率介于l1和l2之间,然鹅比l1斜率更小的第一条直线是l2,不是PQ,与前提矛盾,所以不存在这样的PQ。

那么就可以保证从l1旋转到l2时,受影响的就只有l1上的两个点。

#include<bits/stdc++.h>
using namespace std;
const double pi=3.14159265358979;
const int maxn=1005;
const int M=1000010;
struct point{
    double x,y;
    point(double _x=0,double _y=0){x=_x,y=_y;}
    friend inline point operator +(const point &a,const point &b){
        return point(a.x+b.x,a.y+b.y);
    }
    friend inline point operator -(const point &a,const point &b){
        return point(a.x-b.x,a.y-b.y);
    }
    friend inline double operator *(const point &a,const point &b){
        return (a.x*b.y-a.y*b.x);
    }
}p[maxn];
struct line{
	int s,t;
	double k;
}b[M];
int n,i,j,tot=0;
double ans=1e18;
int id[maxn],pos[maxn];
bool cmp(point a,point b){
	if(a.x==b.x) return a.y<b.y;
	return a.x<b.x;
}
bool Cmp(line a,line b){
	return a.k<b.k;
}
double area(point u,line v){
	return fabs((p[v.s]-u)*(p[v.t]-u))*0.5;
}
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) id[i]=pos[i]=i;
	for(int i=1;i<=n;++i){
		for(int j=i+1;j<=n;++j){
			++tot;
			b[tot].s=i,b[tot].t=j;
			if(p[i].x==p[j].x) b[tot].k=1e18;
			else b[tot].k=(p[i].y-p[j].y)/(p[i].x-p[j].x);
		}
	}
	sort(b+1,b+tot+1,Cmp);
	//pos[i]存的是p[i]的排名 
	//id[i]存的是 排名为i的点 
	for(int i=1;i<=tot;++i){
		int j=pos[b[i].s],k=pos[b[i].t];
		if(j>k) swap(j,k);
		if(j>1) ans=min(ans,area(p[id[j-1]],b[i]));
		if(k<n) ans=min(ans,area(p[id[k+1]],b[i]));
		if(ans==0) break; 
		swap(pos[b[i].s],pos[b[i].t]);
		swap(id[j],id[k]);
	}
	printf("%0.2f\n",ans);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值