gym102460 2019ICPC台湾L Largest Quadrilateral

题目:

给定平面上 n n n个点 ( x i , y i ) (x_i,y_i) (xi,yi),找四个点组成四边形,问组成的四边形的最大面积是多少。注意会有重点,四边形可以退化。
( 4 ≤ n ≤ 4096 , 0 ≤ x i , y i ≤ 1 0 9 ) (4 \le n \le 4096,0 \le x_i,y_i \le 10^9) (4n4096,0xi,yi109)

题解:

首先,能取到凸四边形肯定不会取凹四边形。
先用点集搞出凸包,如果凸包上少于三个点,那么只能退化为一条线,答案为0;如果凸包上只有三个点,而其他点都在凸包内部,所以只能组成凹四边形或者退化成三角形;如果至少有四个点,那么说明可以组成凸四边形,显然面积最大的四边形的四个点都在凸包上。考虑枚举四边形的一条对角线,那么四边形就可以看成是两个三角形拼起来的,我们使这两个三角形的面积最大即可。先看一边,考虑怎么使三角形面积最大,底边已经确定,所以我们找出一个点,到底边的距离最大即可,借鉴一下旋转卡壳的思路,由于凸包的凸性,所以一边的点到枚举的对角线的距离是一个単峰函数,所以当我们确定对角线的其中一个点,遍历枚举另一个点时,也就是对角线在绕一个点旋转时,到对角线距离最大的点也会单调的旋转,所以搞双指针维护即可。
由于给定的都是整点,而要求的是面积,所以面积或是一个整数,或是一个.5的小数。那么我们在算三角形面积的时候不除以2,最后特判整数或.5即可。实现的时候全用long long,无精度损失。

复杂度: O ( n 2 ) O(n^2) O(n2)
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<bitset>
#include<sstream>
#include<ctime>
//#include<chrono>
//#include<random>
//#include<unordered_map>
using namespace std;

#define ll long long
#define ls o<<1
#define rs o<<1|1
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
const double pi=acos(-1.0);
const double eps=1e-6;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const int maxn=5005;
ll read(){
	ll x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
struct Point{
	ll x,y;
	Point(ll x=0,ll y=0):x(x),y(y){}
};
typedef Point Vector;
Vector operator-(Vector a,Vector b){
	return Vector(a.x-b.x,a.y-b.y);
}
ll Cross(Vector a,Vector b){
	return a.x*b.y-a.y*b.x;
}
ll Area(Point a,Point b,Point c){
	return abs(Cross(b-a,c-a));
}
bool operator<(const Point &a,const Point &b){
	if(a.x==b.x)return a.y<b.y;
	else return a.x<b.x;
}
int used[maxn],stk[maxn];
int Andrew(Point *p,int n,Point *h,int *id){
	int tp=0;
	sort(p,p+n);
	for(int i=0;i<n;i++)used[i]=0;
	stk[++tp]=0;
	for(int i=1;i<=n-1;i++){
		while(tp>=2&&Cross(p[stk[tp]]-p[stk[tp-1]],p[i]-p[stk[tp]])<=0){
			used[stk[tp--]]=0;
		}
		used[i]=1;
		stk[++tp]=i;
	}
	int tmp=tp;
	for(int i=n-2;i>=0;i--){
		if(!used[i]){
			while(tp>tmp&&Cross(p[stk[tp]]-p[stk[tp-1]],p[i]-p[stk[tp]])<=0){
				used[stk[tp--]]=0;
			}
			used[i]=1;
			stk[++tp]=i;
		}
	}
	for(int i=1;i<=tp;i++){
		h[i-1]=p[stk[i]];
		id[i-1]=stk[i];
	}
	return tp-1;
}
ll RotateCalipers(Point *p,int n){
	ll res=0;
	for(int i=0;i<n;i++){
		int j=(i+1)%n,jj=(i+3)%n;
		for(int k=(i+2)%n;k!=i;k=(k+1)%n){
			while(Area(p[i],p[j],p[k])<Area(p[i],p[(j+1)%n],p[k]))j=(j+1)%n;
			while(Area(p[i],p[jj],p[k])<Area(p[i],p[(jj+1)%n],p[k]))jj=(jj+1)%n;
			res=max(res,Area(p[i],p[j],p[k])+Area(p[i],p[jj],p[k]));
		}
	}
	return res;
}
int T,n;
Point p[maxn],h[maxn];
int id[maxn];
int main(void){
	// freopen("in.txt","r",stdin);
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		for(int i=0;i<n;i++){
			scanf("%lld%lld",&p[i].x,&p[i].y);
		}
		int num=Andrew(p,n,h,id);
		if(num<3){
			puts("0");
			continue;
		}
		ll ans=0;
		if(num==3){
			for(int i=0;i<n;i++){
				int flag=0;
				for(int j=0;j<num;j++){
					if(i==id[j]){
						flag=1;
						break;
					}
				}
				if(flag)continue;
				ll s=Area(h[0],h[1],h[2]);
				ll mn=min(Area(p[i],h[1],h[2]),min(Area(h[0],p[i],h[2]),Area(h[0],h[1],p[i])));
				ans=max(ans,s-mn);
			}
		}
		else{
			ans=RotateCalipers(h,num);
		}
		if(ans%2==0)printf("%lld\n",ans/2);
		else printf("%lld.5\n",ans/2);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值