凸包问题(含叉积讲解)

预备知识

叉积

1.概念的引入

在平面中我们为了度量一条直线的倾斜状态,为引入倾斜角这个概念。而通过在直角坐标系中建立tan α = k,我们实现了将几何关系和代数关系的衔接,这其实也是用计算机解决几何问题的一个核心,计算机做的是数值运算,因此你需要做的就是把几何关系用代数关系表达出来。而在空间中,为了表示一个平面相对空间直角坐标系的倾斜程度,我们利用一个垂直该平面的法向量来度量(因为这转化成了描述直线倾斜程度的问题)。

2.定义

在这里插入图片描述
在这里插入图片描述

3.应用

(1)求解三角形(平行四边形)面积

在这里插入图片描述
相对海伦公式更精准(减少了开根号的误差)
基于这个结论还可以推出n边形面积计算公式
在这里插入图片描述
严谨的来说,用叉乘算面积也是能够算出负值的
在这里插入图片描述

(2)点定位

在这里插入图片描述

(3)极角排序

极角,指的就是以x轴正半轴为始边,建立极坐标,逆时针转过的角,这个角的范围是[0,2π]。

实现方法:

1.用叉积计算极角(精度高,时间慢)
struct point
{
    double x,y;
    point(double x=0, double y=0):x(x), y(y){}
    point operator - (const point &t)const
    {
        return point(x-t.x, y-t.y);
    }//a - b
    double operator *(const point &t)const
    {
        return x*t.x + y*t.y;
    }//a * b
    double operator ^(const point &t)const
    {
        return x*t.y - y*t.x;
    }//a X b
};
double compare(point a,point b,point c)//计算极角 ab × ac 
{
    return (b-a)^(c-a);
}
bool cmp(point a,point b)
{
	double f=compare(p[pos],a,b);
	if(f==0) return a.x-p[pos].x<b.x-p[pos].x;
	else if(f>0) return true;
	else return false;
}

如果取的点不是边角的点,那么需要先按照象限排序。

int Quadrant(point a)//象限排序,注意包含四个坐标轴
{
    if(a.x>0&&a.y>=0)  return 1;
    if(a.x<=0&&a.y>0)  return 2;
    if(a.x<0&&a.y<=0)  return 3;
    if(a.x>=0&&a.y<0)  return 4;
}


bool cmp2(point a,point b)//先象限后极角
{
    if(Quadrant(a)==Quadrant(b))//返回值就是象限
        return cmp(a,b);
    else Quadrant(a)<Quadrant(b);
}

2.atan2函数(时间快,精度较差)

atan2(y,x)函数返回的是原点至点(x,y)的方位角,即与 x 轴的夹角。也可以理解为复数 x+yi 的辐角。返回值的单位为弧度,这里的这个极角的范围是(−π,π], 一二象限为正,三四象限为负(结果为正表示从 X 轴逆时针旋转的角度,结果为负表示从 X 轴顺时针旋转的角度)。所以我们从小到大排完序后,实际上是 第三象限<第四象限<第一象限<第二象限

struct point
{
	double x,y;
	double angle;
	bool operator <(const point &t)
	{
		return angle<t.angle;
	}
}p[N];
bool cmp(point a,point b)
{
	if(a.angle==b.angle) return a.x<b.x;
	else
	{
		return a.angle<b.angle;
	}
}
for(int i=1;i<=n;i++)
{
	cin>>p[i].x>>p[i].y;
	p[i].angle=atan2(p[i].y,p[i].x);
}
sort(a+1,a+1+n,cmp);


凸包

1.定义

凸包(Convex Hull)是一个计算几何(图形学)中的概念。
在一个实数向量空间V中,对于给定集合X,所有包含X的凸集.的交集S被称为X的凸包。X的凸包可以用X内所有点(X1,…Xn)的凸组合来构造.
在二维欧几里得空间中,凸包可想象为一条刚好包着所有点的橡皮圈。
用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点。

⒈对于一个集合D,D中任意有限个点的凸组合的全体称为D的凸包。
⒉对于一个集合D,所有包含D的凸集之交称为D的凸包。
可以证明,上述两种定义是等价的

数学定义:设S为欧几里得空间 的任意子集。包含S的最小凸集称为S的凸包,记作conv(S)。

概念
1  点集Q的凸包(convex hull)是指一个最小凸多边形,满足Q中的点或者在多边形边上或者在其内。图1中由红色线段表示的多边形就是点集Q={p0,p1,…p12}的凸包。
2  一组平面上的点,求一个包含所有点的最小的凸多边形,这就是凸包问题了。这可以形象地想成这样:在地上放置一些不可移动的木桩,用一根绳子把他们尽量紧地圈起来,并且为凸边形,这就是凸包了。
在这里插入图片描述

2.求解方法

Graham’s Scan法

时间复杂度:O(nlogn)
思路:
先找到凸包上的一个点,然后从那个点开始按逆时针方向逐个找凸包上的点,实际上就是进行极角排序,然后对其查询使用。
步骤:
在这里插入图片描述

1.把所有点放在二维坐标系中,则纵坐标最小的点一定是凸包上的点,如图中的P0。
2.把所有点的坐标平移一下,使 P0 作为原点,如上图。
3.计算各个点相对于 P0 的幅角 α ,按从小到大的顺序对各个点排序。当 α 相同时,距离 P0 比较近的排在前面。例如上图得到的结果为 P1,P2,P3,P4,P5,P6,P7,P8。我们由几何知识可以知道,结果中第一个点 P1 和最后一个点 P8 一定是凸包上的点。
(以上是准备步骤,以下开始求凸包)
以上,我们已经知道了凸包上的第一个点 P0 和第二个点 P1,我们把它们放在栈里面。现在从步骤3求得的那个结果里,把 P1 后面的那个点拿出来做当前点,即 P2 。接下来开始找第三个点:
4.连接P0和栈顶的那个点,得到直线 L 。看当前点是在直线 L 的右边还是左边。如果在直线的右边就执行步骤5;如果在直线上,或者在直线的左边就执行步骤6。
5.如果在右边,则栈顶的那个元素不是凸包上的点,把栈顶元素出栈。执行步骤4。
6.当前点是凸包上的点,把它压入栈,执行步骤7。
7.检查当前的点 P2 是不是步骤3那个结果的最后一个元素。是最后一个元素的话就结束。如果不是的话就把 P2 后面那个点做当前点,返回步骤4。

模板

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#define N 100005
#define INF 0x3f3f3f3f
#define IO ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;

struct point
{
    double x,y;
};

point p[N];
point stackk[N];
int xx,yy;

bool cmp1(point a,point b)
{
    if(a.y == b.y)
        return a.x<b.x;
    else
        return a.y<b.y;
}

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

double dis(point a,point b)
{
    return sqrt((a.x-b.x)*(a.x-b.x)*1.0+(a.y-b.y)*(a.y-b.y)*1.0);
}

bool cmp2(point a,point b)
{
    if(atan2(a.y-yy,a.x-xx) != atan2(b.y-yy,b.x-xx))
        return (atan2(a.y-yy,a.x-xx))<(atan2(b.y-yy,b.x-xx));
    return a.x<b.x;
}

bool cmp(point a,point b)
{
    double m=cross(p[0],a,b);
    if(m>0)
        return 1;
    else if(m==0 && dis(p[0],a)-dis(p[0],b)<=0)
        return 1;
    else
        return 0;
}

int main()
{
    //IO;
    //freopen("D:\\in.txt","r",stdin);
    int n;
    scanf("%d",&n);
    for(int i=0; i<n; i++)
    {
        scanf("%lf%lf",&p[i].x,&p[i].y);
    }
    memset(stackk,0,sizeof(stackk));
    sort(p,p+n,cmp1);
    stackk[0]=p[0];
    xx = stackk[0].x;
    yy = stackk[0].y;
    sort(p+1,p+n,cmp);//用哪种极角排序要看题目的具体要求
    stackk[1]=p[1];
    int top=1;
    for(int i=2; i<n; i++)
    {
        while(i>=1 && cross(stackk[top-1],stackk[top],p[i])<0)
            top--;
        stackk[++top] = p[i];
    }
    double ans=0;
    for(int i=1; i<=top; i++)
    {
        ans += dis(stackk[i-1],stackk[i]);
    }
    ans += dis(stackk[top],stackk[0]);
    printf("%.2lf",ans);
    return 0;
}

3.练习

二维凸包模板题:P2742[USACO5.1]圈奶牛Fencing the Cows .

二维凸包拓展题:P3829 [SHOI2012]信用卡凸包 .
思路:这题转换一下发现就是将每个行用卡的四个圆心丢进凸包求周长然后再加上一个圆的周长就是答案。
然后旋转计算公式:(逆时针为正方向旋转,角度为b)
x’ = xcosb - ysinb
y’ = ycosb + xsinb
证明也很简单,如图
在这里插入图片描述
然后求出圆心坐标丢进凸包板子求出周长,这题注意一下删除共线重复的点(开始没改能ac但样例2没过就很离谱 ,改了后就没问题了)

ac代码

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#define N 100005
#define INF 0x3f3f3f3f
#define IO ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define pi acos(-1)
using namespace std;

struct point
{
    double x,y;
    point(double xx=0,double yy=0): x(xx),y(yy){}
    point operator-(const point&p)const
    {
		return point(x-p.x,y-p.y);
	}
	point operator+(const point&p)const
	{
		return point(x+p.x,y+p.y);
	}
};

point p[N];
point stackk[N];
int xx,yy;

bool cmp1(point a,point b)
{
    if(a.y == b.y)
        return a.x<b.x;
    else
        return a.y<b.y;
}

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

double dis(point a,point b)
{
    return sqrt((a.x-b.x)*(a.x-b.x)*1.0+(a.y-b.y)*(a.y-b.y)*1.0);
}

bool cmp2(point a,point b)
{
    if(atan2(a.y-yy,a.x-xx) != atan2(b.y-yy,b.x-xx))
        return (atan2(a.y-yy,a.x-xx))<(atan2(b.y-yy,b.x-xx));
    return a.x<b.x;
}

bool cmp(point a,point b)
{
    double m=cross(p[1],a,b);
    if(m>0)
        return 1;
    else if(m==0 && dis(p[1],a)-dis(p[1],b)<=0)
        return 1;
    else
        return 0;
}

point Rotate(point p,double angle)
{
    double co = cos(angle),si = sin(angle);
    return point(p.x*co-p.y*si,p.y*co+p.x*si);
}

int cnt=0;
int main()
{
    //IO;
    //freopen("D:\\in.txt","r",stdin);
    int n;
    double a,b,r;
    scanf("%d",&n);
    scanf("%lf%lf%lf",&b,&a,&r);
    a = a/2.0-r, b = b/2.0-r;
    for(int i=0; i<n; i++)
    {
        double x,y,theta;
        scanf("%lf%lf%lf",&x,&y,&theta);
        point center = point(x,y);
        point p1=point(a,b),p2=point(a,-b),p3=point(-a,b),p4=point(-a,-b);
        p[++cnt]=Rotate(p1,theta)+center;
        p[++cnt]=Rotate(p2,theta)+center;
        p[++cnt]=Rotate(p3,theta)+center;
        p[++cnt]=Rotate(p4,theta)+center;
    }
    //memset(stackk,0,sizeof(stackk));
    sort(p+1,p+cnt+1,cmp1);
    stackk[1]=p[1];
    sort(p+2,p+cnt+1,cmp);
    int top=1;
    for(int i=2; i<=cnt; i++)
    {
        while(top>1 && cross(stackk[top],stackk[top-1],p[i])>=0)
            top--;
        stackk[++top] = p[i];
    }
    stackk[top+1]=p[1];
    double ans=2.0*pi*r;
    for(int i=1; i<=top; i++)
    {
        ans += dis(stackk[i],stackk[i+1]);
    }
    //ans += dis(stackk[top],stackk[0]);
    printf("%.2lf",ans);
    return 0;
}

三维凸包:P2287 [HNOI2004]最佳包裹.


参考资料

计算几何讲义——叉积.
数学:凸包算法详解

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值