【BZOJ3707】圈地
Description
2维平面上有n个木桩,黄学长有一次圈地的机会并得到圈到的土地,为了体现他的高风亮节,他要使他圈到的土地面积尽量小。圈地需要圈一个至少3个点的多边形,多边形的顶点就是一个木桩,圈得的土地就是这个多边形内部的土地。(因为黄学长非常的神,所以他允许圈出的第n点共线,那样面积算0)
Input
第一行一个整数n,表示木桩个数。
接下来n行,每行2个整数表示一个木桩的坐标,坐标两两不同。
Output
仅一行,表示最小圈得的土地面积,保留2位小数。
Sample Input
3
0 0
0 1
1 0
0 0
0 1
1 0
Sample Output
0.50
HINT
对于100%的数据,n<=1000。
题解:假如我们已经确定了三角形的一条边,那么面积可以表示成 边长*高/2,如果我们将所选的边当做y轴,那么显然第3个点应取|x|最小的点。问题是如何快速确定|x|最小的点。
有一个结论(难想),就是将所有直线按照极角排序(斜率也行),将所有点按y排序(相当于所选的边是x轴),此时所有点距离直线的相对位置是确定的。我们枚举每条直线,当我们从ai,bi枚举到ai+1,bi+1时,只有ai,bi的相对位置发生了改变,其余点的相对位置均不改变。(相对位置指的是以所选直线为y轴后,x的大小关系。这个结论自己画画应该就能理解)
于是我们用桶维护每个点的相对位置即可。
#include <cstdio> #include <iostream> #include <algorithm> #include <cmath> using namespace std; const int maxn=1010; int n,tot; int s[maxn],pos[maxn]; double ans; struct point { double x,y; point () {} point (double a,double b){x=a,y=b;} point operator + (const point &a) const {return point(x+a.x,y+a.y);} point operator - (const point &a) const {return point(x-a.x,y-a.y);} double operator * (const point &a) const {return x*a.y-y*a.x;} }p[maxn]; struct line { double k; int a,b; }l[1000000]; inline int rd() { int ret=0,f=1; char gc=getchar(); while(gc<'0'||gc>'9') {if(gc=='-')f=-f; gc=getchar();} while(gc>='0'&&gc<='9') ret=ret*10+gc-'0',gc=getchar(); return ret*f; } bool cmpy(point a,point b) { return a.y<b.y; } bool cmpk(line a,line b) { return a.k<b.k; } void calc(int a,int b,int c) { double S=fabs((p[b]-p[a])*(p[c]-p[a])/2); ans=min(ans,S); } int main() { n=rd(); int i,j; for(i=1;i<=n;i++) p[i].x=rd(),p[i].y=rd(); sort(p+1,p+n+1,cmpy); for(i=1;i<=n;i++) s[i]=pos[i]=i; for(i=1;i<=n;i++) for(j=i+1;j<=n;j++) l[++tot].k=atan2(p[j].y-p[i].y,p[j].x-p[i].x),l[tot].a=i,l[tot].b=j; sort(l+1,l+tot+1,cmpk); ans=999999999; for(i=1;i<=tot;i++) { if(pos[l[i].a]>pos[l[i].b]) swap(l[i].a,l[i].b); if(pos[l[i].a]>1) calc(s[pos[l[i].a]-1],l[i].a,l[i].b); if(pos[l[i].b]<n) calc(s[pos[l[i].b]+1],l[i].a,l[i].b); swap(pos[l[i].a],pos[l[i].b]); s[pos[l[i].a]]=l[i].a,s[pos[l[i].b]]=l[i].b; } printf("%.2lf",ans); return 0; }