【BZOJ 3707】圈地

【题目】

题目描述:

2 2 2 维平面上有 n n n 个木桩,黄学长有一次圈地的机会并得到圈到的土地,为了体现他的高风亮节,他要使他圈到的土地面积尽量小。圈地需要圈一个至少 3 3 3 个点的多边形,多边形的顶点就是一个木桩,圈得的土地就是这个多边形内部的土地。(因为黄学长非常的神,所以他允许圈出的第 n n n 点共线,那样面积算 0 0 0

输入格式:

第一行一个整数 n n n,表示木桩个数。

接下来 n n n 行,每行 2 2 2 个整数表示一个木桩的坐标,坐标两两不同。

输出格式:

仅一行,表示最小圈得的土地面积,保留2位小数。

样例数据:

输入
3
0 0
0 1
1 0

输出
0.50

提示:

对于 100 % 100\% 100% 的数据, n ≤ 1000 n≤1000 n1000


【分析】

暴力算法很显然, n 3 n^3 n3 枚举每个点来计算答案。

考虑一下如何优化一下暴力算法。

我们枚举两个点 a , b a,b a,b,如果把点 a , b a,b a,b 所在直线看做 y y y 轴的话,可以看出使当前面积最小的那个点就是距离当前坐标系的 y y y 轴最近的一个点(也就是在当前坐标系中横坐标绝对值最小的那个点)。

如果我们能够快速的得知最近的点的话,就可以将复杂度降低到 O ( n 2 ) O(n^2) O(n2)

对于点,我们先按照 x x x 从小到大排序,这样可以知道各个点之间的相对关系。

把这些点两两之间求出一条直线,记录这条直线是哪两个点取到的,记录这条直线的斜率 k k k。然后按照 k k k 从小到大排序,我们可以依次按照当前 k k k 的顺序处理这些直线。

可以发现当我们在处理到直线 ( a , b ) (a,b) (a,b) 时, a , b a,b a,b 在序列中是相邻的(假设 a a a 在序列中位置在 b b b 前面),且离 ( a , b ) (a,b) (a,b) 最近的点就是序列中 a a a 左边的点或 b b b 右边的点,即可计算答案。且此时相当于 b b b 在当前坐标系中刚刚比 a a a 小(按排序定义),所以交换 a , b a,b a,b 在序列中的位置即可处理下一条直线了。


【代码】

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1005
using namespace std;
int pos[N],id[N];
struct point
{
	double x,y;
	point(){}
	point(double x,double y):x(x),y(y){}
	point operator+(const point &a){return point(x+a.x,y+a.y);}
	point operator-(const point &a){return point(x-a.x,y-a.y);}
	friend double dot(const point &a,const point &b){return a.x*b.x+a.y*b.y;}
	friend double cross(const point &a,const point &b){return a.x*b.y-a.y*b.x;}
	friend bool operator<(const point &a,const point &b){return (a.x==b.x)?a.y<b.y:a.x<b.x;}
}p[N];
struct line
{
	int s,t;
	double k;
	friend bool operator<(const line &a,const line &b){return a.k<b.k;}
}l[N*N];
double area(point P,line L)
{
	return 0.5*fabs(cross((p[L.s]-P),(p[L.t]-P)));
}
int main()
{
	int n,i,j;
	scanf("%d",&n);
	for(i=1;i<=n;++i)
	  scanf("%lf%lf",&p[i].x,&p[i].y);
	sort(p+1,p+n+1);
	int tot=0;
	for(i=1;i<=n;++i)  pos[i]=id[i]=i;
	for(i=1;i<=n;++i)
	{
		for(j=i+1;j<=n;++j)
		{
			l[++tot].s=i,l[tot].t=j;
			if(p[i].x==p[j].x)  l[tot].k=1e18;
			else  l[tot].k=(p[i].y-p[j].y)/(p[i].x-p[j].x);
		}
	}
	sort(l+1,l+tot+1);
	double ans=1e18;
	for(i=1;i<=tot;++i)
	{
		int x=pos[l[i].s];
		int y=pos[l[i].t];
		if(x>y)  swap(x,y);
		if(x>1)  ans=min(ans,area(p[id[x-1]],l[i]));
		if(y<n)  ans=min(ans,area(p[id[y+1]],l[i]));
		swap(pos[l[i].s],pos[l[i].t]);
		swap(id[x],id[y]);
	}
	printf("%.2lf",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值