计算几何——凸包//极角排序//旋转卡壳

凸包

假设平面上有p0~p12共13个点,过某些点作一个多边形,使这个多边形能把所有点都“包”起来。当这个多边形是凸多边形的时候,我们就叫它“凸包”。

在这里插入图片描述

卷包裹法

先找一个最边缘的点(一般位于最下方,如果有多个点,则选择最左方的点),假设有一条绳子,以该点为端点向右边逆时针旋转直到碰到另一个点为止,此时找出凸包的一条边;然后再用新找到的点作为端点,继续旋转绳子,找到下一个端点;重复这一步骤直至围成一个凸多边形,即可得到这个点集的凸包。
时间复杂度为O(n^2)
在这里插入图片描述

旋转卡壳

例题:Beauty Contest

Bessie, Farmer John’s prize cow, has just won first place in a bovine beauty contest, earning the title ‘Miss Cow World’. As a result, Bessie will make a tour of N (2 <= N <= 50,000) farms around the world in order to spread goodwill between farmers and their cows. For simplicity, the world will be represented as a two-dimensional plane, where each farm is located at a pair of integer coordinates (x,y), each having a value in the range -10,000 … 10,000. No two farms share the same pair of coordinates.

Even though Bessie travels directly in a straight line between pairs of farms, the distance between some farms can be quite large, so she wants to bring a suitcase full of hay with her so she has enough food to eat on each leg of her journey. Since Bessie refills her suitcase at every farm she visits, she wants to determine the maximum possible distance she might need to travel so she knows the size of suitcase she must bring.Help Bessie by computing the maximum distance among all pairs of farms.

Input

  • Line 1: A single integer, N

  • Lines 2…N+1: Two space-separated integers x and y specifying coordinate of each farm

Output

  • Line 1: A single integer that is the squared distance between the pair of farms that are farthest apart from each other.

Sample Input
4
0 0
0 1
1 1
1 0
Sample Output
2

Hint
Farm 1 (0, 0) and farm 3 (1, 1) have the longest distance (square root of 2)

题意:
给出n个坐标,计算去这些坐标之间的最长距离。

常规方法:
求那两个点距离最大,由于N最大有50000个,直接枚举是不行的,这时可以建立一个凸包,在凸包里面的肯定不是最远两个端点中的一个,相距最远的一定在凸包里,只要枚举凸包里的点就行了,这样把很多不必须的计算都省去了,节约了大量的时间。
在逐步建立凸包的时候,每加一个点可以判断下是否与前面的两点构成凹型,是的话把前一个点删掉就行了。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<vector>
#include<algorithm>
#define eps 1e-8
using namespace std;

const int N = 5e4+10;

double add(double a, double b)
{
	if(abs(a+b) < eps * (abs(a) + abs(b)))
		return 0;
	return a + b;
}

struct Point 
{
	double x, y;
	Point(){}
	Point (double x, double y):x(x), y(y){}
	Point operator +(Point p)
	{
		return Point(add(x, p.x), add(y, p.y));
	}
	Point operator -(Point p)
	{
		return Point(add(x, -p.x), add(y, -p.y));
	}
	Point operator *(double d)
	{
		return Point(x*d, y*d);
	}
	double dot(Point p)	//内积 (点积)
	{
		return add(x*p.x, y*p.y);
	}
	double det(Point p)	//外积 (叉积) 
	{
		return add(x*p.y, -y*p.x);
	}
};

Point ps[N];
int n;
bool cmp(Point x, Point y)
{
	if(x.x != y.x)
		return x.x < y.x;
	else
		return x.y < y.y;
}

vector<Point> convex_hull(Point *ps, int n)
{
	sort(ps, ps+n, cmp);
	int k = 0;
	vector<Point> qs(n*2);
	for(int i = 0; i < n; ++i)	//下侧 
	{
		while(k > 1 && (qs[k-1] - qs[k-2]).det(ps[i] - qs[k-1]) <= 0)
			k--;
		qs[k++] = ps[i];
	}
	for(int i = n-2, t = k; i >= 0; --i)	//上侧 
	{
		while(k > t && (qs[k-1] - qs[k-2]).det(ps[i] - qs[k-1]) <= 0)
			k--;
		qs[k++] = ps[i];
	}
	qs.resize(k-1);
	return qs;
}

double dist(Point p1, Point p2)
{
	return (p1-p2).dot(p1-p2);
}

void solve()
{
	vector<Point> qs = convex_hull(ps, n);
	double res = 0;
	for(int i = 0; i < qs.size(); ++i)
	{
		for(int j = 0; j < i; ++j)
		{
			res = max(res, dist(qs[i], qs[j]));
		}
	}
	printf("%.0f\n", res); 
}

int main()
{
	scanf("%d", &n);
	for(int i = 0; i < n; ++i)
		scanf("%lf %lf", &ps[i].x, &ps[i].y);
	solve();
}
  

旋转卡壳:
但凸包的点也有很大时,上面的方法就不好用了,这时可以用旋转卡(qiǎ)壳法,能在线性时间内算出来。
假设最远点对是p和q,那么p就是点集中(p-q)方向最远的点,而q是点集中(q-p)方向最远的点,因此可以按照逆时针改变方向,同时枚举出所有对于某个方向上最远的点,那么最远点一定也包含在其中。

在这里插入图片描述

极角排序

在平面内取一个定点O,叫极点,引一条射线Ox,叫做极轴,再选定一个长度单位和角度的正方向(通常取逆时针方向)。

对于平面内任何一点M,用ρ表示线段OM的长度(有时也用r表示),θ表示从Ox到OM的角度,ρ叫做点M的极径,θ叫做点M的极角,有序数对 (ρ,θ)就叫点M的极坐标。

那么给定平面上的一些点,把它们按照一个选定的中心点排成顺(逆)时针。

存储

struct point//存储点
{
    double x,y;
};

double cross(double x1,double y1,double x2,double y2) //计算叉积
{
    return (x1*y2-x2*y1);
}

double compare(point a,point b,point c)//计算极角
{
    return cross((b.x-a.x),(b.y-a.y),(c.x-a.x),(c.y-a.y));
}

排序方法

atan2()函数

时间比叉积快

关于atan2()函数:在C语言的math.h或C++中的cmath中有两个求反正切的函数atan(double x)与atan2(double y,double x) 他们返回的值是弧度要转化为角度再自己处理下。

前者接受的是一个正切值(直线的斜率)得到夹角,但是由于正切的规律性本可以有两个角度的但它却只返回一个,因为atan的值域是从-90~90 也就是它只处理一四象限,所以一般不用它。

第二个atan2(double y,double x) 其中y代表已知点的Y坐标,同理x ,返回值是此点与远点连线与x轴正方向的夹角,这样它就可以处理四个象限的任意情况了,它的值域相应的也就是-180~180了

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

叉积

精度比atan2()高

关于叉积:叉积=0是指两向量平行(重合);叉积>0,则向量a在向量b的顺时针方向(粗略的理解为在a在b的下方);叉积<0,则向量a在向量b的逆时针方向(粗略的理解为在a在b的上方)

bool cmp2(point a,point b) 
{
    point c;//原点
    c.x = 0;
    c.y = 0;
    if(compare(c,a,b)==0)//计算叉积,函数在上面有介绍,如果叉积相等,按照X从小到大排序
        return a.x<b.x;
    else return compare(c,a,b)>0;
}

象限

在有特殊需求的时候才会用到

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 cmp3(point a,point b)  //先按象限从小到大排序 再按极角从小到大排序
{
    if(Quadrant(a)==Quadrant(b))//返回值就是象限
        return cmp1(a,b);
    else Quadrant(a)<Quadrant(b);
}

例题

Space Ant

The most exciting space discovery occurred at the end of the 20th century. In 1999, scientists traced down an ant-like creature in the planet Y1999 and called it M11. It has only one eye on the left side of its head and just three feet all on the right side of its body and suffers from three walking limitations:

1.It can not turn right due to its special body structure.
2.It leaves a red path while walking.
3.It hates to pass over a previously red colored path, and never does that.

The pictures transmitted by the Discovery space ship depicts that plants in the Y1999 grow in special points on the planet. Analysis of several thousands of the pictures have resulted in discovering a magic coordinate system governing the grow points of the plants. In this coordinate system with x and y axes, no two plants share the same x or y.
An M11 needs to eat exactly one plant in each day to stay alive. When it eats one plant, it remains there for the rest of the day with no move. Next day, it looks for another plant to go there and eat it. If it can not reach any other plant it dies by the end of the day. Notice that it can reach a plant in any distance.
The problem is to find a path for an M11 to let it live longest.
Input is a set of (x, y) coordinates of plants. Suppose A with the coordinates (xA, yA) is the plant with the least y-coordinate. M11 starts from point (0,yA) heading towards plant A. Notice that the solution path should not cross itself and all of the turns should be counter-clockwise. Also note that the solution may visit more than two plants located on a same straight line.
在这里插入图片描述
Input
The first line of the input is M, the number of test cases to be solved (1 <= M <= 10). For each test case, the first line is N, the number of plants in that test case (1 <= N <= 50), followed by N lines for each plant data. Each plant data consists of three integers: the first number is the unique plant index (1…N), followed by two positive integers x and y representing the coordinates of the plant. Plants are sorted by the increasing order on their indices in the input file. Suppose that the values of coordinates are at most 100.

Output
Output should have one separate line for the solution of each test case. A solution is the number of plants on the solution path, followed by the indices of visiting plants in the path in the order of their visits.

Sample Input
2
10
1 4 5
2 9 8
3 5 9
4 1 7
5 3 2
6 6 3
7 10 10
8 8 1
9 2 4
10 7 6
14
1 6 11
2 11 9
3 8 7
4 12 8
5 9 20
6 3 2
7 1 6
8 2 13
9 15 1
10 14 17
11 13 19
12 5 18
13 7 3
14 10 16
Sample Output
10 8 7 3 4 9 5 6 2 1 10
14 9 10 11 5 12 8 7 6 13 4 14 1 3 2

题意:
M11生物吃一种植物,吃了后一整天不会动,现在有若干个点分布这些植物。M11只能左拐,寻找一条存活最长的路径,即求凸包问题。

思路:
卷包裹法

#include<iostream>
#include<memory.h>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define eps 1e-8
using namespace std;

const int maxn = 100;
struct point 
{
	double x, y;
	int num;
};

point p[maxn];
bool vis[maxn]; //标记是否已经取到
int ans[maxn];	//保存取的顺序

int m, n, k;
 
bool cmp(const point p1, const point p2)
{
	return p1.y == p2.y && p1.x < p2.x || p1.y < p2.y;
} 

int sgn(double x)
{
	if(fabs(x) < eps)
		return 0;
	return x < 0 ? -1 : 1;
}

//p1p3在p1p2的左侧的时候小于0 
double get_direction(point p1, point p2, point p3)
{
	return (p3.x-p1.x)*(p2.y-p1.y) - (p2.x-p1.x)*(p3.y-p1.y); 
}

//两点之间的距离 
double get_distance(point p1, point p2)
{
	return sqrt((p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y));
}

//卷包裹法 
void jarvis()
{
	memset(vis, false, sizeof(vis));
	
	//p[0]是构造出来的
	//由题意可知,第一个点在y轴上 
	vis[0] = vis[1] = true;
	
	//optimal保存最优的(最靠外的)点,cur是当前点 
	int optimal, cur = 1;
	for(int i = 2; i <= n; ++i)
	{
		for(int j = 1; j <= n; ++j)
		{
			if(!vis[j])
			{
				//从待选择的点中随便找一个点让optimal等于它
				//因为下面要和其他点比较 
				optimal = j;
				break;
			}
		}
		for(int j = 1; j <= n; ++j)
		{
			if(!vis[j] && optimal != j && sgn(get_direction(p[cur], p[j], p[optimal])) <= 0)
			{
				//如果共线 
				if(sgn(get_direction(p[cur], p[j], p[optimal])) == 0)
				{
					//判断距离小的点是哪个 
					if(get_distance(p[cur], p[j]) < get_distance(p[cur], p[optimal]))
					{
						optimal = j;
					}
				}
				else
					optimal = j;
			}
		}
		vis[optimal] = true;
		cur = optimal;
		ans[k++] = p[optimal].num;
	}
}

int main()
{
	scanf("%d", &m);
	while(m--)
	{
		k = 0;
		memset(ans, 0, sizeof(ans));
		scanf("%d", &n);
		for(int i = 1; i <= n; ++i)
			scanf("%d%lf%lf", &p[i].num, &p[i].x, &p[i].y);
		sort(p+1, p+1+n, cmp);
		
		//构造第一个点 
		p[0].num = 0;
		p[0].x = 0;
		p[0].y = p[1].y;
		
		ans[k++] = p[1].num;
		jarvis();
		printf("%d ", n);
		for(int i = 0; i < k; ++i)
			printf("%d ", ans[i]);
		puts("");
	}
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值