小棕圈地 凸包旋转卡壳

小粽圈地

问题描述
小粽家里有一块地,地上有 n 个木桩。小粽家的地可以看作是一个平面,并且小粽知道每个木桩的坐标 (xi,yi)。
小粽很喜欢四边形,现在她想从这些木桩中选出 4 个来围成一个四边形(这个四边形为简单多边形,即每条边不能和自己相交,但不一定要为凸四边形),并使得这个四边形的面积最大。请你帮小粽算出这个最大值是多少。
输入格式

第一行一个正整数 n 表示木桩的大小。
接下来 n 行,第 i 行位两个实数 xi,yi,描述了第 i 个木桩的坐标。

输出格式

输出一行一个实数,表示围出的最大的四边形的面积。保留三位小数。

输入样例1

5
0 0
1 0
1 1
0 1
0.5 0.5

输出样例1

1.000

样例2
点此下载。

数据范围及约定
20% 的数据满足 n ≤100;
60% 的数据满足 n ≤400;
80% 的数据满足 n ≤1500;
100% 的数据满足 n ≤5000,所有坐标都在 int 范围内。
提示
[显然,答案是在凸包上的]
[可以考虑枚举一条对角线,再设法确定另外两点的位置,例如注意到点的位置具有单峰性。]
[更可考虑求对角线与凸包的切点,利用单调性均摊复杂度]

题解

法一:暴力枚举法

枚举四个点。n4

法二:对角线枚举法

枚举两个点 n3

法三:单调性的函数可以用三分法求取最大值。

n2 logn

法四 :旋转卡壳法

利用凸多边形的性质。

先考虑子问题:对于一个凸多边形,如何在其中找一个面积最大的三角形。

固定三角形其中一个点,枚举另一个底边另一个点。底边确定,在切点可以达到最大值。利用暴力确定切点在哪里。且由于该极值点存在单调性。

两重循环,枚举对角线就可以了。时间复杂度为n2

助教的旋转凸包(顺时针)代码写法如下:

double solve(Point p[], int n)
{
double ans = 0;
for(int i=0; i<n; i++)
	p[i+n] = p[i];  //复制一份方便代码编写
for(int i=0; i<n; i++){
	int a=i,b=i+1;   
	//旋转卡壳法,以ij为对角线进行枚举
	for(int j=i+1; j<n; j++){
			while ( cross(p[a+1]- p[a],p[j]-p[i]) < 0 )
						a++;
			while ( cross(p[b+1]-p[b],p[i]-p[j]) < 0 )
						b++; 
			ans = max(ans,cross(p[b]-p[a], p[j]-p[i])); 
		}
	}  
return ans/2;
}

注意下面我的代码传入的凸包是逆时针方向的

关于凸包的顺时针逆时针的传入顺序问题可以看我的下一篇文章

题解

//旋转卡壳法。

#include <vector>
#include <stdio.h>
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;


struct Point {
	double x, y;
	Point(double x = 0, double y = 0) : x(x), y(y) {}

	void read()
	{
		scanf("%lf%lf", &x, &y);
	}

	void print()
	{
		printf("%lf %lf\n", x, y);
	}
};

Point operator + (const Point& a, const Point& b)
{
	return Point(a.x + b.x, a.y + b.y);
}

Point operator - (const Point& a, const Point& b)
{
	return Point(a.x - b.x, a.y - b.y);
}

double cross(Point a, Point b)
{
	return a.x * b.y - a.y * b.x;
}

bool cmp(const Point& a, const Point& b)
{
    return a.x==b.x ? a.y<b.y : a.x<b.x;
}

/* ============ 代码实现开始 ==========*/

// p, n: 传入点集及大小
// ch: 返回的凸包
// 返回值:凸包的大小
int getConvexhull(Point p[], int n, Point ch[])
{
    sort(p,p+n,cmp);
    
    int m=0;
    //求下凸壳
    for(int i=0; i<n; ++i){
        for(;m>1&&cross(ch[m-1]-ch[m-2],p[i]-ch[m-2])<0;--m) ;
        ch[m++]=p[i];
    }

    //求上凸壳
    int t=m;
    for(int i=n-2; i>=0; --i){
        for(;m>t&&cross(ch[m-1]-ch[m-2],p[i]-ch[m-2])<0;--m) ;
        ch[m++]=p[i];
    }
//    for(int i=0; i<m-1; i++)
//        printf("(%lf,%lf)\n",ch[i].x,ch[i].y);
    return m-1;
    //返回值为凸包内点的数目
}

// p, n: 传入的凸包及大小
// 返回最终答案
double solve(Point p[], int n)
{
    double ans = 0;
    
    for(int i=0; i<n; i++)
        p[i+n] = p[i];  //复制一份方便代码编写
    for(int i=0; i<n; i++){
        //int a=i,b=i+1;
        int a=1,b=1;
        //旋转卡壳法,以ij为对角线进行枚举
        for(int j=i+1; j<n; j++){
            while ( /*(a+1)%n!=j &&*/ cross(p[a+1]-p[i],p[j]-p[i])>cross(p[a]-p[i],p[j]-p[i]) )
                a++;
            while ( /*(b+1)%n!=i &&*/ cross(p[j]-p[i],p[b+1]-p[i])>cross(p[j]-p[i],p[b]-p[i]) )
                b++;
            
            ans = max(ans,cross(p[j]-p[i],p[b]-p[a]));
        }
    }
    return ans/2;
}

/* ============ 代码实现结束 ==========*/


#define maxn 5000

Point p[2 * maxn + 10], ch[2 * maxn + 10];

int main()
{

	int n;
	scanf("%d", &n);
	for (int i = 0; i < n; i++)
		p[i].read();

	printf("%.3lf\n", solve(ch, getConvexhull(p, n, ch)));
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值