计算几何
凸包
给大家推荐一手凸包好博客
https://blog.csdn.net/linxilinxilinxi/article/details/81810944
1.定义:给定一些点,求能把所有这些点包含在内的面积最小的多边形,如果是凸多边形则称为凸包;
2.解决方法:
一是 : Graham扫描法 O(nlogn)
二是 : Jarvis步进法 O(nh) h是凸包上的顶点数
基本思路 : “旋转扫除”,设定一个参照顶点,逐个旋转到其他顶点,并判断这些顶点是否在凸包上
3.模板代码:
Graham扫描法的变种Andrew算法(更快更稳)
算法做两次扫描,先从最左边的点沿下凸包扫描到最右边,再从最右边的点扫描上凸包扫描到最左边;上凸包和下凸包连起来就是完整的凸包;
具体步骤 :
1 . 将所有点按照横坐标x从小到大进行排序,如果x相同,按y从小到大排序,并且删除重复的点;得到序列{p0,p1,p2,p3…pm}
2 . 从左到右扫描所有点,求下凸包;p0一定在凸包上,是最左边的顶点;然后依次检查{p1,p2,p3…pm},扩展出下凸包;
判断依据 : 如果新点在凸包前进方向的左边,说明在下凸包上,把它加入到凸包;如果在右边,说明拐弯了,删除最后加入凸包的点,继续这个过程,知道检查完所有点;
是否拐弯 : 利用叉积判断(插图)
3 . 从右到左重新扫描所有点求上凸包;
O(nlogn)
4.例题:
hdu 1392 “Surround the Trees”
全解查看博客题解偏
https://blog.csdn.net/gpc_123/article/details/122741291
int Convex_hull(Point *p,int n,Point *ch){
sort(p, p + n);//对点排序
n = unique(p, p + n) - p;//去重
int v = 0;
//求下凸包,如果p[i]是右拐弯,这个点不在凸包上往回退
for (int i = 0; i < n;i ++){
while(v > 1 && sgn(Cross(ch[v - 1] - ch[v - 2],p[i] - ch[v - 2])) <= 0)
v--;
ch[v++] = p[i];
}
int j = v;
//求上凸包
for (int i = n - 2; i >= 0;i --){
while(v > j && sgn(Cross(ch[v - 1] - ch[v - 2],p[i] - ch[v - 2])) <= 0)
v--;
ch[v++] = p[i];
}
if(n > 1) v--;
return v; //返回凸包顶点数
}
判断点是否在凸包内部
如果这个点在里面,这个点跟围栏上任意相邻的两个点组成的三角形面积之和就等于凸包的面积,
如果在外面,就大于凸包的面积,
#include<iostream>
#include<string>
#include<cstring>
#include<map>
#include<queue>
#include<algorithm>
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define PII pair<int,int>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
int idx = 1;
double sum = 0.0;
const double pi = acos(-1.0);
const double eps = 1e-8;
const int maxn = 1e5 + 10;
int sgn(double x) {
if (fabs(x) < eps)return 0;
else return x < 0 ? -1 : 1;
}
struct Point {
double x, y;
Point() {}
Point(double x, double y) : x(x), y(y) {}
Point operator+(Point B) { return Point(x + B.x, y + B.y); }
Point operator-(Point B) { return Point(x - B.x, y - B.y); }
Point operator*(double k) { return Point(x * k, y * k); }
Point operator/(double k) { return Point(x / k, y / k); }
bool operator==(Point B) { return sgn(x - B.x) == 0 && sgn(y - B.y) == 0; }
friend bool operator < (Point a,Point b){
return sgn(a.x - b.x) < 0 || (sgn(a.x - b.x) == 0 && sgn(a.y - b.y) < 0);
}
};
Point a[maxn], b[maxn], c[maxn];
typedef Point Vector;
double Distance(Point A, Point B) { return hypot(A.x - B.x, A.y - B.y);}
double Dot(Vector A, Vector B) { return A.x * B.x + A.y * B.y;}
double Cross(Vector A, Vector B) { return A.x * B.y - A.y * B.x;}
double area(Point a, Point b, Point c)//求三个点组成的三角形面积
{
double e = Distance(a,b);
double f = Distance(a,c);
double g = Distance(b,c);
double p = (e+f+g) / 2.0;
return sqrt(p*(p-e)*(p-f)*(p-g));
}
bool check(Point x, int v)//判断x这个 点是否在凸包中
{
double cnt = 0.0;
for(int i = 1; i < v; i++)
cnt += area(x, b[i], b[i-1]);
cnt += area(x, b[v-1], b[0]);
if(fabs(cnt - sum) < 0.9) return true;//精度注意
return false;
}
int Convex_hull(Point *p,int n,Point *ch){
sort(p, p + n);
n = unique(p, p + n) - p;
int v = 0;
//求下凸包
for (int i = 0; i < n;i ++){
while(v > 1 && sgn(Cross(ch[v - 1] - ch[v - 2],p[i] - ch[v - 2])) <= 0)
v--;
ch[v++] = p[i];
}
int j = v;
//求上凸包
for (int i = n - 2; i >= 0;i --){
while(v > j && sgn(Cross(ch[v - 1] - ch[v - 2],p[i] - ch[v - 2])) <= 0)
v--;
ch[v++] = p[i];
}
if(n > 1) v--;
return v;
}
int main(){
int t;
scanf("%d", &t);
while(t--){
sum = 0.0;
int n, m;
cin >> n >> m;
for (int i = 0; i < n;i ++) cin >> a[i].x >> a[i].y;
int v = Convex_hull(a, n, b);
for (int i = 2; i < v;i ++){
sum += area(b[0], b[i - 1], b[i]);
}
for (int i = 0; i < m;i ++)
cin >> c[i].x >> c[i].y;
cout << "Case " << idx++ <<endl;
for(int i = 0; i < v; i++)
cout << b[i].x << ' ' << b[i].y << endl;
cout << b[0].x << ' ' << b[0].y << endl;
for(int i = 0; i < m; i++)
{
Point x = c[i];
cout << x.x << ' ' << x.y;
if(check(x,v))
cout << " is unsafe!" << endl;
else
cout << " is safe!" << endl;
}
cout << endl;
}
return 0;
}
凹凸多边形
1.凸多边形:
1、没有任何一个内角是优角(没有一个角超过180°)的多边形。
2、就是把一个多边形任意一边向两方无限延长成为一条直线,如果多边形的其他各边均在此直线的同旁,那么这个多边形就叫做凸多边形
3、凸多边形是一个内部为凸集的简单多边形。
4、一个多边形,如果它的任意两个点的连线都不包括该多边形以外的点,就称为凸多边形。
在日常生活中比较常见的平行四边形、矩形、菱形、正方形等图形,都属于凸四边形。
凸多边形的内角均小于或等于180°,边数为n(n属于Z且n大于2)的凸多边形内角和为(n-2)×180°,但任意凸多边形外角和均为360°
2.凹多边形:
1、至少有一个优角(角大于180°)的多边形
2、把一个各边不自交的多边形任意一边向两方无限延长成为一直线,如果多边形其他各边不在此直线的同旁,那么这个多边形就叫做凹多边形。
3、凹多边形的是一个内部为非凸集的简单多边形.简单多边形的下列性质与其凸性等价。
(1)一个内角大于180度。
(2)存在两个顶点间的线段位于多边形的外部。
(3)多边形内存在两个点,其连线不全部在多边形内部。
五角星、四角星、八角星、六角形等都是凹多边形
因此凹多边形的内角和 ,也适用( N-2 )X 180° 这个公式
凹多边形的外角和是: 360+ 大于 180 度的内角的个数 *180
3.区分凹凸四边形:
1、角度法:
判断每个顶点所对应的内角是否小于180度,如果小于180度,则是凸的,如果大于180度,则是凹多边形。
2、凸包法:
这种方法首先计算这个多边形的凸包,关于凸包的定义在此不再赘述,首先可以肯定的是凸包肯定是一个凸多边形。如果计算出来的凸多边形和原始多边形的点数一样多,那就说明此多边形时凸多边形,否则就是凹多边形。
3、顶点凹凸性法
利用以当前顶点为中心的矢量叉乘或者计算三角形的有符号面积判断多边形的方向以及当前顶点的凹凸性。
假设当前连续的三个顶点分别是P1,P2,P3。计算向量(P1,P2),(P1,P3)的叉乘,也就是计算三角形P1P2P3的面积,得到的结果如果大于0,则表示P2点在线段P1和P3的右侧,多边形的顶点是逆时针序列。然后依次计算下一个前后所组成向量的叉乘,如果在计算时,出现负值,则此多边形时凹多边形,如果所有顶点计算完毕,其结果都是大于0,则多边形时凸多边形。
4、辛普森面积法
利用待判别的顶点以及前后两个顶点所组成的三角形,利用辛普森公式计算其面积,如果此三角形面积与整个多边形面积符号相同,那么这个顶点是凸的;如果此三角形面积与整个多边形面积符号不同,那么这个顶点是凹的,即整个多边形也是凹多边形。
半平面交
**1.概念 : **
半平面就是一个平面的一半;一个半平面用一条有向直线来定义,定义有向直线左边的平面是它代表的半平面;
给定一些半平面就会形成半平面相交的区域;半平面相交形成的区域一定是凸多边形(可能不闭合);
求解半平面相交问题就是求解形成的凸多边形;
半平面的定义 : (表示半平面的有向直线)
struct Line{
Point p; //直线上一个点
Vector v; //方向向量,左边是半平面
double ang; //极角,从x正半轴旋转到v的角度
Line(){};
Line(Point p,Vector v):p(p),v(v){ang = atan2(v.y,v.x);}
bool operator < (Line &L){return ang < L.ang;}//用于排序
}
**2.方法 : **
1.增量法O(n^2)
2.学习方法O(nlogn);
算法步骤 :
1.对所有半平面进行极角排序
2.依次加入每一个半平面;使用双端队列记录构成凸四边形的半平面;队首指向最早加入凸四边形的半平面,尾部指向新加入的半平面
一共有四种情况 : 1.可以直接加入队列 2.覆盖原队首 3.覆盖原队尾 4.不能加入队列;
3.典例 :
hdu 2297 (Run)
题解详见博客
https://blog.csdn.net/gpc_123/article/details/122753505
struct Line{
Point p; //直线上一个点
Vector v; //方向向量,左边是半平面
double ang; //极角,从x正半轴旋转到v的角度
Line(){};
Line(Point p,Vector v):p(p),v(v){ang = atan2(v.y,v.x);}
friend bool operator < (Line a,Line b){return a.ang < b.ang;}//用于排序
};
//p在线L的左边
bool OnLeft(Line L,Point p){return sgn(Cross(L.v,p - L.p )) > 0;}
Point Cross_point(Line a,Line b){ //两直线的交点
Vector u = a.p - b.p;
double t = Cross(b.v, u) / Cross(a.v, b.v);
return a.p + a.v * t;
}
vector<Point> HPI(vector<Line> L){
int n=L.size();
sort(L.begin(),L.end());//将所有半平面按照极角排序。
int first,last;
vector<Point> p(n);
vector<Line> q(n);
vector<Point> ans;
q[first=last=0]=L[0];
for(int i=1;i<n;i++){
while(first<last&&!OnLeft(L[i],p[last-1]))last--;//删除顶部的半平面
while(first<last&&!OnLeft(L[i],p[first]))first++;//删除底部的半平面
q[++last]=L[i];//将当前的半平面假如双端队列顶部。
if(fabs(Cross(q[last].v,q[last-1].v))<eps){//对于极角相同的,选择性保留一个。
last--;
if(OnLeft(q[last],L[i].p))q[last]=L[i];
}
if(first<last)p[last-1]=Cross_point(q[last-1],q[last]);//计算队列顶部半平面交点。
}
while(first<last&&!OnLeft(q[first],p[last-1]))last--;//删除队列顶部的无用半平面。
if(last-first<=1)return ans;//半平面退化
p[last]=Cross_point(q[last],q[first]);//计算队列顶部与首部的交点。
for(int i=first;i<=last;i++)ans.push_back(p[i]);//将队列中的点复制。
return ans;
}
最近点对
问题 : 在给定的平面上,找到距离最近的两个点
思路 : 分治法O(nlogn)
-
划分,按照x的坐标排序,然后依次划分成更小的子集(每个子集最后只有一个或者两个点)
-
解决,在每一个子集中找到最近点对
-
合并,在求出子集s1,s2的最近点对后,合并s1和s2;合并的时候有以下两种情况;
-
集合S中的最近点对在子集S1内部或者S2内部,直接合并S1和S2
-
这两个点一个在S1中,一个在S2中,不能简单合并.设S1中的最短距离是d1,S2中最短距离是d2,在S1和S2的中间点p[mid] 附近找到所有离它小于d1和d2的点(按照x坐标值进行计算距离)记录在点集tmp_p[]中,这样那么最近点对就在这些点中;
之后先按y坐标值对tmp_p[]的点排序,然后剪枝去掉不符合要求的点;
-
例题 : hdu1007
详解博客 : https://blog.csdn.net/gpc_123/article/details/122764318
double Closest_Pair(int left,int right){
double dis = INF;
if(left == right) return dis; //只剩一个点
if(left + 1 == right) //只剩两个点
return Distance(p[left], p[right]);
int mid = (left + right) / 2; //分治
double d1 = Closest_Pair(left, mid); //求S1内的最近点对
double d2 = Closest_Pair(mid + 1, right); //求S2内的最近点对
dis = min(d1, d2);
int k = 0;
for (int i = left; i <= right;i ++){ //在s1和s2中间附近找可能的最小点对
if(fabs(p[mid].x - p[i].x) <= dis) //按x坐标来找
tmp_p[k++] = p[i];
}
sort(tmp_p, tmp_p + k, cmpy); //按照y坐标进行排序
for (int i = 0; i < k;i ++){
for (int j = i + 1; j < k;j ++){
if(tmp_p[j].y - tmp_p[i].y >= dis) break;
dis = min(dis, Distance(tmp_p[i], tmp_p[j]));
}
}
return dis;
}
旋转卡壳
定义 :
两条平行线与凸包的交点称为对踵点对;找对踵点对的方法称为旋转卡壳;
- 凸包最大距离点对 2. 凸包最近距离点对 3. 最小面积外接矩形 4. 最小周长外接矩形 5. 凸包间的最大距离 6. 凸包间的最小距离
旋转卡壳算法操作 :
- 找初始的对踵点对和平行线 ; 可以找y坐标最大和最小的两个点,经过两个点做两条水平线,一条向左一条向右;
- 同时逆时针旋转两条线,直到一条线与多边形的一边重合,此时得到新的对踵点对;(然后根据题意要求进行计算)
- 重复操作2得到想要结果