原题链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3707
圈地
Description
2维平面上有n个木桩,黄学长有一次圈地的机会并得到圈到的土地,为了体现他的高风亮节,他要使他圈到的土地面积尽量小。圈地需要圈一个至少3个点的多边形,多边形的顶点就是一个木桩,圈得的土地就是这个多边形内部的土地。(因为黄学长非常的神,所以他允许圈出的第n点共线,那样面积算0)
Input
第一行一个整数n,表示木桩个数。
接下来n行,每行2个整数表示一个木桩的坐标,坐标两两不同。
Output
仅一行,表示最小圈得的土地面积,保留2位小数。
Sample Input
3
0 0
0 1
1 0
Sample Output
0.50
HINT
对于100%的数据,n<=1000。
题解
每次更新最小三角形时,我们不用O(n)枚举,事实上我们只需要找到离当前线段最近的两个点就行了。那怎么找这两个点呢,我们只需要维护一个“从左到右”排好序的序列就好了。先以y轴为基准排序,之后O(n^2)枚举出所有可能的边,再以这些边为基准旋转坐标系,就能从序列中O(1)找出线段左右的两点,因为每次旋转时,实际上只有作为基准线段的两点在序列中的位置发生了变化,只需要交换两点即可。
另外,我们可以将平面分为若干块,在块内O(n^3)暴力求最小三角形,显然正确性堪忧,所以随机旋转坐标系若干次。。。期望得分100。
代码
#include<bits/stdc++.h>
using namespace std;
const int M=1005;
const double eps=1e-8;
int sig(double x){return (x>eps)-(x<-eps);}
struct pt{int x,y;};
struct li{int x,y;double k;};
bool operator < (pt a,pt b){return a.x<b.x;}
bool operator < (li a,li b){return a.k<b.k;}
pt operator - (pt a,pt b){return (pt){a.x-b.x,a.y-b.y};}
double operator * (pt a,pt b){return a.x*1.0*b.y-a.y*1.0*b.x;}
int n,pos[M],rank[M],tot;
double ans=1e200;
pt p[M];
li line[M*M*5];
void in()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d%d",&p[i].x,&p[i].y);
sort(p+1,p+1+n);
}
void ml()
{
for(int i=1;i<=n;++i)pos[i]=rank[i]=i;
for(int i=1;i<n;++i)
for(int j=i+1;j<=n;++j)
{
line[++tot].x=i;line[tot].y=j;
line[tot].k=(p[j].y-p[i].y)/(double)(p[j].x-p[i].x);
}
sort(line+1,line+1+tot);
}
void ac()
{
int t1,t2;
for(int i=1;i<=tot;++i)
{
t1=line[i].x;t2=line[i].y;
if(pos[t1]>pos[t2])swap(t1,t2);
if(pos[t1]>1)ans=min(ans,fabs((p[t1]-p[t2])*(p[rank[pos[t1]-1]]-p[t2])));
if(pos[t2]<n)ans=min(ans,fabs((p[t1]-p[t2])*(p[rank[pos[t2]+1]]-p[t2])));
swap(pos[t1],pos[t2]);
swap(rank[pos[t1]],rank[pos[t2]]);
}
printf("%.2lf",ans/2.0);
}
int main()
{
in();
ml();
ac();
return 0;
}