Given n distinct points on a plane, your task is to find the triangle that have the maximum area, whose vertices are from the given points.
Input
The input consists of several test cases. The first line of each test case contains an integer n, indicating the number of points on the plane. Each of the following n lines contains two integer xi and yi, indicating the ith points. The last line of the input is an integer −1, indicating the end of input, which should not be processed. You may assume that 1 <= n <= 50000 and −104 <= xi, yi <= 104 for all i = 1 . . . n.
Output
For each test case, print a line containing the maximum area, which contains two digits after the decimal point. You may assume that there is always an answer which is greater than zero.
Sample Input
3 3 4 2 6 2 7 5 2 6 3 9 2 0 8 0 6 5 -1
Sample Output
0.50 27.00
题意: 给出n个点构成的点集,求这些点构成的最大三角形的面积。
分析: 直接枚举复杂度显然过高了,由于点数是1e4级别的,应该用O(n^2)以下的算法。由于面积最大三角形的三个顶点一定出现在凸包顶点上(证明:(4条消息) 旋转卡壳算法总结_Cloth的博客-CSDN博客),因此可以对点集先求个凸包,之后枚举三角形第一个顶点,第二个顶点和第三个顶点就用类似旋转卡壳的思路去求,n*n正好是n^2的复杂度。具体实现是这样的,第一重循环枚举第一个顶点,第二重循环枚举第二个顶点,这时候已经得到三角形的底边了,第三个顶点就是平行于这条底边的凸包切线上的切点,同时随着第二个顶点在枚举时第三个顶点位置也是不减的,和旋转卡壳思想一致,因此O(n)实现枚举第二个点以及第三个点。
具体代码如下:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
const double eps = 1e-8;
const double inf = 1e20;
const double pi = acos(-1.0);
const int maxp = 151000;
//`Compares a double to zero`
int sgn(double x)
{
if(fabs(x) < eps)return 0;
if(x < 0)return -1;
else return 1;
}
//square of a double
inline double sqr(double x){return x*x;}
struct Point
{
double x, y;
Point(){}
Point(double _x,double _y){x = _x, y = _y;}
void input(){scanf("%lf%lf",&x,&y);}
void output(){printf("%.2f %.2f\n",x,y);}
bool operator == (Point b)const{return sgn(x-b.x) == 0 && sgn(y-b.y) == 0;}
bool operator < (Point b)const{return sgn(x-b.x)== 0?sgn(y-b.y)<0:x<b.x;}
Point operator -(const Point &b)const{return Point(x-b.x,y-b.y);}
//叉积
double operator ^(const Point &b)const{return x*b.y - y*b.x;}
//点积
double operator *(const Point &b)const{return x*b.x + y*b.y;}
//返回长度
double len(){return sqrt(x*x+y*y);/*库函数*/}
//返回长度的平方
double len2(){return x*x + y*y;}
//返回两点的距离
double distance(Point p){return Point(x-p.x,y-p.y).len();}
Point operator +(const Point &b)const{return Point(x+b.x,y+b.y);}
Point operator *(const double &k)const{return Point(x*k,y*k);}
Point operator /(const double &k)const{return Point(x/k,y/k);}
};
struct Line
{
Point s,e;
Line(){}
Line(Point _s,Point _e){s = _s, e = _e;}
bool operator ==(Line v){return (s == v.s)&&(e == v.e);}
//`根据一个点和倾斜角angle确定直线,0<=angle<pi`
Line(Point p,double angle)
{
s = p;
if(sgn(angle-pi/2) == 0){e = (s + Point(0,1));}
else{e = (s + Point(1,tan(angle)));}
}
// 点在线段上的判断
bool pointonseg(Point p){return sgn((p-s)^(e-s)) == 0 && sgn((p-s)*(p-e)) <= 0;}
};
struct polygon
{
int n;
Point p[maxp];
Line l[maxp];
void input(int _n){
n = _n;
for(int i = 0;i < n;i++)
p[i].input();
}
void add(Point q){
p[n++] = q;
}
void getline(){
for(int i = 0;i < n;i++){
l[i] = Line(p[i],p[(i+1)%n]);
}
}
struct cmp{
Point p;
cmp(const Point &p0){p = p0;}
bool operator()(const Point &aa,const Point &bb){
Point a = aa, b = bb;
int d = sgn((a-p)^(b-p));
if(d == 0){
return sgn(a.distance(p)-b.distance(p)) < 0;//极角相同,距离原点近的靠前
}
return d > 0;
}
};
//`进行极角排序`
//`首先需要找到最左下角的点`
//`需要重载号好Point的 < 操作符(min函数要用) `
void norm(){
Point mi = p[0];
for(int i = 1;i < n;i++)mi = min(mi,p[i]);
sort(p,p+n,cmp(mi));
}
//`得到凸包`
//`得到的凸包里面的点编号是0$\sim$n-1的`
//`两种凸包的方法`
//`注意如果有影响,要特判下所有点共点,或者共线的特殊情况`
//`测试 LightOJ1203 LightOJ1239`
void getconvex(polygon &convex){
sort(p,p+n);
convex.n = n;
for(int i = 0;i < min(n,2);i++){
convex.p[i] = p[i];
}
if(convex.n == 2 && (convex.p[0] == convex.p[1]))convex.n--;//特判
if(n <= 2)return;
int &top = convex.n;
top = 1;
for(int i = 2;i < n;i++){
while(top && sgn((convex.p[top]-p[i])^(convex.p[top-1]-p[i])) <= 0)
top--;
convex.p[++top] = p[i];
}
int temp = top;
convex.p[++top] = p[n-2];
for(int i = n-3;i >= 0;i--){
while(top != temp && sgn((convex.p[top]-p[i])^(convex.p[top-1]-p[i])) <= 0)
top--;
convex.p[++top] = p[i];
}
if(convex.n == 2 && (convex.p[0] == convex.p[1]))convex.n--;//特判
convex.norm();//`原来得到的是顺时针的点,排序后逆时针`
}
//`得到凸包的另外一种方法`
//`测试 LightOJ1203 LightOJ1239`
//如果要保留所有共线点需要极角排序后最后一条边上的点逆序处理且出栈条件去掉等号
//更改一下最后一条边的排序顺序
// int k;
// for(k = a.n-1; k >= 0; k--)
// if(sgn((a.p[k]-a.p[0])^(a.p[k-1]-a.p[0])) != 0)
// break;
// reverse(a.p+k, a.p+a.n);
void Graham(polygon &convex){
norm();
int &top = convex.n;
top = 0;
if(n == 1){
top = 1;
convex.p[0] = p[0];
return;
}
if(n == 2){
top = 2;
convex.p[0] = p[0];
convex.p[1] = p[1];
if(convex.p[0] == convex.p[1])top--;
return;
}
convex.p[0] = p[0];
convex.p[1] = p[1];
top = 2;
for(int i = 2;i < n;i++){
while( top > 1 && sgn((convex.p[top-1]-convex.p[top-2])^(p[i]-convex.p[top-2])) <= 0 )
top--;
convex.p[top++] = p[i];
}
if(convex.n == 2 && (convex.p[0] == convex.p[1]))convex.n--;//特判
}
//`判断是不是凸的`
bool isconvex(){
bool s[3];
memset(s,false,sizeof(s));
for(int i = 0;i < n;i++){
int j = (i+1)%n;
int k = (j+1)%n;
s[sgn((p[j]-p[i])^(p[k]-p[i]))+1] = true;
if(s[0] && s[2])return false;
}
return true;
}
//`得到周长`,点要相邻
//`测试 LightOJ1239`
double getcircumference(){
double sum = 0;
for(int i = 0;i < n;i++)
sum += p[i].distance(p[(i+1)%n]);
return sum;
}
//`得到面积`,点要相邻
double getarea()
{
double sum = 0;
for(int i = 0;i < n;i++)
sum += (p[i]^p[(i+1)%n]);
return fabs(sum)/2;
}
//`判断点和任意多边形的关系`
//` 3 点上`
//` 2 边上`
//` 1 内部`
//` 0 外部`
int relationpoint(Point q){
for(int i = 0;i < n;i++){
if(p[i] == q)return 3;
}
getline();
for(int i = 0;i < n;i++){
if(l[i].pointonseg(q))return 2;
}
int cnt = 0;
for(int i = 0;i < n;i++){//图形学上的内外测试
int j = (i+1)%n;
int k = sgn((q-p[j])^(p[i]-p[j]));//向右侧引一条射线
int u = sgn(p[i].y-q.y);
int v = sgn(p[j].y-q.y);
if(k > 0 && u < 0 && v >= 0)cnt++;//向量两点y值小的被截断一部分
if(k < 0 && v < 0 && u >= 0)cnt--;
}
return cnt != 0;
}
};
polygon a, con;
signed main()
{
int n;
while(cin >> n)
{
if(n == -1)
break;
a.input(n);
a.Graham(con);
a = con;
//本题需要三倍原来顶点数据
for(int i = 0; i < con.n; i++)
a.add(con.p[i]);
for(int i = 0; i < con.n; i++)
a.add(con.p[i]);
double ans = 0.0;
//枚举第一个点
for(int k = 0; k < con.n; k++)
{
//由于距离底边最远的点具有单调性,因此可以旋转卡壳
for(int i = k+1, j = i+1; i < k+con.n-1; i++)//枚举第二个点
{
while(((a.p[i]-a.p[k])^(a.p[j]-a.p[k])) < ((a.p[i]-a.p[k])^(a.p[j+1]-a.p[k])))
j++;
double la = (a.p[i]-a.p[k]).len();
double lb = (a.p[k]-a.p[j]).len();
double lc = (a.p[j]-a.p[i]).len();
double p = (la+lb+lc)/2;
ans = max(ans, sqrt(p*(p-la)*(p-lb)*(p-lc)));
}
}
printf("%.2f\n", ans);
}
return 0;
}