目录
前言
- 不用总想着一下子就学完所有知识,刷题要紧(能遇到的东西才是重点!!!)
- 刷的越多,才越知道考什么,怎么考。
- 只是三个问题最重要,最常用:
- 凸多边形直径
- 最小面积外接矩形
- 凸多边形见最小距离
- 旋转卡壳解题关键:怎么卡,其他就是套模板了(其实模板内容也不过是一些数学常识)
参考博客
- 好极了,甚至让我有了想要好好学习的冲动。
- 本博文任何知识都可以优先参考该博客
前置知识
1.极角排序
- 简单得很,设置一个Point mi;全局变量就便变成了写普通的数组排序函数了
//极角排序,这样就ok了
Point mi;
bool cmp(Point a, Point b) {
double x = sgn(cross(mi, a, b));
if (x == 0) return sgn(dis(mi, a) - dis(mi, b)) < 0;
return x > 0;
}
//不知道怎么回事,上面这题,在https://vjudge.net/problem/POJ-1113里面就是不对。
//下面这种就对了。
struct cmp {
Point p;
cmp() {}
cmp(const Point &p0) { p = p0; }
//以点p为极坐标原点进行极角排序:sort(,,p.cmp(mi));
bool operator()(const Point &a, const Point &b) {
int x = sgn(cross(p, a, b));
if (x == 0)
return sgn(dis(a, p) - dis(b, p)) < 0;
else
return x > 0;
}
};
- 为什么上面那种不对,还是后面再来思考吧:破案了,原来是因为我在函数里面又新加了Point mi;emmm就变成了局部变量。
2.凸包(默认逆时针)
- 参考博客:数学:凸包算法详解
- 定义:凸的多边形,包含所有给定点
- 解法: Graham扫描法(参考博客[数学:凸包算法详解]中有图!!!,静态动态都有!!!)
- 时间复杂度:O( n l o g n nlogn nlogn)
- 思路:
1)先找到一个点(肯定在凸包上的点) p 0 p_0 p0放入栈中;
2)然后进行某种排序(排序要求后面再说,着重关注标注部分)得到 p 1 , p 2 , . . . , p n − 1 p1,p2,...,p_{n-1} p1,p2,...,pn−1;
3)排序之后先将 p 1 p_1 p1放入栈顶,然后从 p 2 p_2 p2一直更新到 p n − 1 p_{n-1} pn−1为止( p n − 1 p_{n-1} pn−1一定在凸包上!!!)。
4)另外,更新:假设栈顶点为 p p p,然后不断判断待更新点 p i p_i pi是否在 p 0 p → \overrightarrow{p_0p} p0p的左边(左边:放入栈中,判断下一位。右边: p p p弹出栈,直到 p i p_i pi在 p 0 p → \overrightarrow{p_0p} p0p在左边或者遇到 p 0 p_0 p0) - 排序思路1(极角排序):纵坐标最小的点 p 0 p_0 p0一定是凸包上的点,以 p 0 p_0 p0为极坐标源点,对其他点进行极角排序(按相对于 p 0 p_0 p0点的幅角从小到大,幅角相等时按到 p 0 p_0 p0的相对距离从小到大排序),得到 p 0 , p 1 , . . . , p n − 1 p_0,p_1,...,p_{n-1} p0,p1,...,pn−1。
- 排序思路2(直角坐标排序——求上下凸包的时候用,如果想在求凸包中用,就要先求上、下凸包 ,然后再拼凑起来(而且还有要注意的东西——点重了或者少了)。距离说明:长宽都平行于坐标轴的正方形): p 0 p_0 p0为最左边的点,然后按 x x x坐标从小到大排序, x x x相等时 y y y坐标从大到小排序。
- 模板:
//由a数组求凸包传给 P ———— 没想到求凸包这么简单,之前想得太难了,艹
// Point *a,我们传送的是a的地址,函数中可以改变数组。a[]呢?
void getconvex(Point *a, int n, polygon &convex) {
mi = a[0];
for (int i = 1; i < n; i++) mi = min(mi, a[i]);
sort(a, a + n, cmp); //极角排序:求凸包的准备工作
int top = 0; // top模拟栈顶
convex.p[top] = a[0];
convex.p[++top] = a[1];
for (int i = 2; i < n; i++) {
while (top > 0 &&
(sgn(cross(convex.p[top - 1], convex.p[top], a[i])) <= 0))
top--;
convex.p[++top] = a[i];
}
convex.n = top + 1;
}
- 另一种求上下凸壳的方法感觉不是很必要。不用学就不学,多花些时间思考这些知识怎么应用才是王道(保持对题目的敏感)。
3.对踵点
- 参考博客:对踵点对
- 切线:
- 对踵点对:(平行切线上的两点)
- (凸包)三种情况:
旋转卡壳能解决的各类问题
能解决的问题:(学基础的,然后剩下的遇到再补)
1.计算距离
1.1凸多边形直径
- 参考博客:旋转卡壳——凸多边形直径
- 定义:多边形上任意两点间的距离的最大值
- 题解:求出凸包之后,直接遍历每条边,求每条边的两个点共有的对踵点(可能1个,可能两个,不知道这里为什么只需要考虑一个,即continue后面的都不需要)。
- 模板:
- 注意!!!。r千万不能从0开始,不然在遍历第一条边的时候就走远了。
- ↑ \uparrow ↑:今天虽然花了将近俩小时在这上面,甚至有点自闭,但是真的值,总算是找出来最后的问题。nice
// diameter:直径(zhijing)
double zhijing(Point *a, int n) {
if (n == 2) return distance(a[0], a[1]); //只有两个点
double res = 0;
int r =
1; //今天就栽在了这里。之前一直另的r=0,在Linr(a[0],a[1])的是时候就会出问题emmm
a[n] = a[0];
for (int i = 0; i < n; i++) {
while (sgn(cross(a[i], a[i + 1], a[r + 1]) -
cross(a[i], a[i + 1], a[r])) > 0)
r = (r + 1) % n;
if (sgn(distance(a[i], a[r]) - res) > 0) res = distance(a[i], a[r]);
if (sgn(distance(a[i + 1], a[r]) - res) > 0)
res = distance(a[i + 1], a[r]);
//这里为保险起见(一条线对应两个点的时候)。好像不要也是对的,为什么?这是一个大问题
continue;
while (sgn(cross(a[i], a[i + 1],
a[r + 1] - cross(a[i], a[i + 1], a[r]))) == 0) {
r = (r + 1) % n;
if (sgn(distance(a[i], a[r]) - res) > 0) res = distance(a[i], a[r]);
if (sgn(distance(a[i + 1], a[r]) - res) > 0)
res = distance(a[i + 1], a[r]);
}
}
return res;
}
- 模板题:P1452 [USACO03FALL]Beauty Contest G /【模板】旋转卡壳
- 题意:给定平面上 nn 个点,求凸包直径。【数据范围】
对于100%的数据, 2 ≤ n ≤ 50000 , ∣ x ∣ , ∣ y ∣ ≤ 1 0 4 2\le n \le 50000,|x|,|y| \le 10^4 2≤n≤50000,∣x∣,∣y∣≤104 。 - 题解:旋转卡壳模板题,不解释
- 代码:
- 题意:给定平面上 nn 个点,求凸包直径。【数据范围】
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
using namespace std;
const int N = 5e4 + 5;
const double eps = 1e-8;
int sgn(double x) {
if (fabs(x) < eps) return 0;
return (x > 0) ? 1 : -1;
}
int n;
struct Point {
double x, y;
Point(double x = 0, double y = 0) : x(x), y(y) {}
void input() { scanf("%lf%lf", &x, &y); }
bool operator<(const Point &b) const {
if (sgn(y - b.y) == 0)
return sgn(x - b.x) < 0;
else
return sgn(y - b.y) < 0;
}
Point operator-(const Point &b) const { return Point(x - b.x, y - b.y); }
double operator^(const Point &b) const { return x * b.y - b.x * y; }
bool operator==(const Point &b) const {
return (sgn(x - b.x)) == 0 && (sgn(y - b.y) == 0);
}
} a[N];
double dis(const Point &a, const Point &b) {
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
struct cmp {
Point p;
cmp() {}
cmp(const Point &p0) { p = p0; }
//以点p为极坐标原点进行极角排序:sort(,,p.cmp(mi));
bool operator()(const Point &a, const Point &b) {
int x = sgn((a - p) ^ (b - p));
if (x == 0)
return sgn(dis(a, p) - dis(b, p)) < 0;
else
return x > 0;
}
};
//向量ab x 向量ac
double cross(Point a, Point b, Point c) { return (b - a) ^ (c - a); }
struct polygon {
int n;
Point p[N];
} convex;
//由a数组求凸包传给 P ———— 没想到求凸包这么简单,之前想得太难了,艹
void getconvex(Point *a, int n, polygon &convex) {
Point mi = a[0];
for (int i = 1; i < n; i++) mi = min(mi, a[i]);
sort(a, a + n, cmp(mi)); //排序:求凸包的准备工作(这里不是极角排序)
int top = 0; // top模拟栈顶
convex.p[top] = a[0];
convex.p[++top] = a[1];
for (int i = 2; i < n; i++) {
while (top > 0 &&
(sgn(cross(convex.p[top - 1], convex.p[top], a[i])) <= 0))
top--;
convex.p[++top] = a[i];
}
convex.n = top + 1;
}
// diameter:直径(zhijing)
double zhijing(Point *a, int n) {
if (n == 2) return dis(a[0], a[1]); //只有两个点
double res = 0;
int r =
1; //今天就栽在了这里。之前一直另的r=0,在Linr(a[0],a[1])的是时候就会出问题emmm
a[n] = a[0];
for (int i = 0; i < n; i++) {
while (sgn(cross(a[i], a[i + 1], a[r + 1]) -
cross(a[i], a[i + 1], a[r])) > 0)
r = (r + 1) % n;
if (sgn(dis(a[i], a[r]) - res) > 0) res = dis(a[i], a[r]);
if (sgn(dis(a[i + 1], a[r]) - res) > 0) res = dis(a[i + 1], a[r]);
//这里为保险起见(一条线对应两个点的时候)。好像不要也是对的,为什么?这是一个大问题
continue;
while (sgn(cross(a[i], a[i + 1],
a[r + 1] - cross(a[i], a[i + 1], a[r]))) == 0) {
r = (r + 1) % n;
if (sgn(dis(a[i], a[r]) - res) > 0) res = dis(a[i], a[r]);
if (sgn(dis(a[i + 1], a[r]) - res) > 0) res = dis(a[i + 1], a[r]);
}
}
return res;
}
signed main() {
cin >> n;
for (int i = 0; i < n; i++) a[i].input();
getconvex(a, n, convex);
// cout << ">>>" << convex.n << endl;
// for (int i = 0; i < convex.n; i++) {
// printf("%.2lf %.2lf\n", convex.p[i].x, convex.p[i].y);
// }
double ans = zhijing(convex.p, convex.n);
printf("%d\n", (int)round(ans * ans));
return 0;
}
1.2凸多边形宽
- 定义:凸多边形的任意两条平行切线间的最短距离。
- 题解:和求凸多边形长几乎一样,但是这里不关注“点-点对踵点”,只关注“点-对对踵点”的最小值。需要用到点到直线的距离公式
- 模板:先略。遇到再说,每遇到就先理解。
1.3凸多边形间最大距离
1.4凸多边形间最小距离
// !注意要先对两凸包的点数组进行极角排序(同时顺时针,同时逆时针都ok)。
// polygon a,b的点和大小
//调用:double ans=minconvextoconvex(a.p,b.p,a.n,b.n)
double minconvextoconvex(Point *a, Point *b, int n, int m) {
a[n] = a[0];
b[m] = b[0];
double res = inf;
int ra = 0, rb = 0;
for (int i = 0; i < n; i++)
if (sgn(a[i].y - a[ra].y) < 0) ra = i;
for (int i = 0; i < m; i++)
if (sgn(b[i].y - b[rb].y) > 0) rb = i;
res = min(res, dis(a[ra], b[rb]));
for (int i = 0; i < n; i++) {
//以下为什么是>????今后再来看看?
//这里确实不同于其他的旋转卡壳,似乎是以b[rb+1]为准,而不是b[rb],得到的依然是距离Line(a[ra],a[ra+1])最小的点b[rb+1]
//!有些东西也是需要记的。别说了,多刷题
while (sgn(cross(a[ra], a[ra + 1], b[rb + 1]) -
cross(a[ra], a[ra + 1], b[rb])) > 0)
rb = (rb + 1) % m;
res = min(res,
dissegtoseg(Line(a[ra], a[ra + 1]), Line(b[rb], b[rb + 1])));
ra = (ra + 1) % n;
}
return res;
}
- 只是这里理解起来有一点点麻烦
2.外接矩形
2.1最小面积外接矩形
- 要求:求凸包的所有外接矩形种的最小值。(又是要求输出矩形各点)
- 解法:(如果不是逆时针凸包就先求凸包),枚举边,卡三个点(上面求长和宽是卡一个点。至于怎么卡,建议自己先想想)
- 具体的见博客:P3187 [HNOI2007]最小矩形覆盖(凸包,旋转卡壳,凸包最小矩形覆盖–求面积及各点)
- 代码:
//https://www.luogu.com.cn/problem/P3187-----P3187 [HNOI2007]最小矩形覆盖
#include <bits/stdc++.h>
#define pb push_back
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
const int N = 5e4 + 5;
const double eps = 1e-8;
const double inf = 1e9;
const double pi = acos(-1.0);
int sgn(double x) {
if (fabs(x) < eps) return 0;
if (x < 0)
return -1;
else
return 1;
}
int n;
struct Point {
double x, y;
Point(double x = 0, double y = 0) : x(x), y(y) {}
// Point() {}
// Point(double _x, double _y) { x = _x; y = _y; }
/*
建议用以上形式。
但是注意,不能:
Point(){}
Point(double x,double y){x=x,y=y;}
!今天的所有错误,都来源于Point(double x,double y){x=x,y=y;}这种用法。
Point(double x=0,double y=0):x(x),y(y){}也没有问题,就是不能上面这种
*/
void input() { scanf("%lf%lf", &x, &y); }
bool operator<(const Point &b) const {
if (sgn(y - b.y) == 0)
return sgn(x - b.x) < 0;
else
return sgn(y - b.y) < 0;
}
Point operator-(const Point &b) const { return Point(x - b.x, y - b.y); }
double operator^(const Point &b) const { return x * b.y - b.x * y; }
double operator*(const Point &b) const { return x * b.x + y * b.y; }
Point operator*(double k) const { return Point(x * k, y * k); }
Point operator+(const Point &b) const { return Point(x + b.x, y + b.y); }
bool operator==(const Point &b) const {
return (sgn(x - b.x)) == 0 && (sgn(y - b.y) == 0);
}
} a[N];
vector<Point> vp;
double distance(Point a, Point b) {
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
struct cmp {
Point p;
cmp() {}
cmp(const Point &p0) { p = p0; }
//以点p为极坐标原点进行极角排序:sort(,,p.cmp(mi));
bool operator()(const Point &a, const Point &b) const {
int x = sgn((a - p) ^ (b - p));
if (x == 0)
return sgn(distance(a, p) - distance(b, p)) < 0;
else
return x > 0;
}
};
//向量ab x 向量ac
double cross(Point a, Point b, Point c) { return (b - a) ^ (c - a); }
double dot(Point a, Point b, Point c) { return (b - a) * (c - a); }
struct polygon {
int n;
Point p[N];
} convex;
struct Line {
Point s, e;
Line() {}
Line(Point _s, Point _e) { s = _s, e = _e; }
//点向式,这里向量可以有很多种表示。
Line(Point p, double angle) {
s = p;
e = s + Point(cos(angle), sin(angle));
}
//有向线段(s,e)倾斜角,(-pi,pi)。直线倾斜角只需要将角度转换为(-pi/2,pi/2)即可
double angle() {
double an = atan2(e.y - s.y, e.x - s.x);
// atan(double)返回值域为(-pi/2,pi/2)
// atan2(y,x)返回值域为(-pi,pi)
return an;
}
};
//由a数组求凸包传给 P ———— 没想到求凸包这么简单,之前想得太难了,艹
void getconvex(Point *a, int n, polygon &convex) {
Point mi = a[0];
for (int i = 1; i < n; i++) mi = min(mi, a[i]);
sort(a, a + n, cmp(mi)); //排序:求凸包的准备工作(这里不是极角排序)
int top = 0; // top模拟栈顶
convex.p[top] = a[0];
convex.p[++top] = a[1];
for (int i = 2; i < n; i++) {
while (top > 0 &&
(sgn(cross(convex.p[top - 1], convex.p[top], a[i])) <= 0))
top--;
convex.p[++top] = a[i];
}
convex.n = top + 1;
}
//点到直线距离,注意加绝对值。
double dispointtoline(Point a, Line b) {
Point s = b.s, e = b.e;
return fabs(cross(s, e, a)) / distance(s, e);
}
//点在直线上的投影
Point progpointtoline(Point a, Line b) {
Point s = b.s, e = b.e;
// dot不是cross,有找了一天的错。百炼成钢
double k = dot(s, e, a) / distance(s, e) / distance(s, e);
return s + (e - s) * k;
}
//调用凸包的p和n
double getminrectanglecover(Point *a, int n) {
if (n < 3) return 0.0;
double res = inf;
int r1 = 1, r2 = 1, r3 = 1;
a[n] = a[0];
for (int i = 0; i < n; i++) {
//卡逆时针第1,2,3个点a[r1],a[r2],a[r3]
//注意别取等
while (sgn((a[i + 1] - a[i]) * (a[r1 + 1] - a[r1])) > 0)
r1 = (r1 + 1) % n;
while (sgn(cross(a[i], a[i + 1], a[r2 + 1]) -
cross(a[i], a[i + 1], a[r2])) >= 0)
r2 = (r2 + 1) % n;
if (i == 0) r3 = r2; //绝活
while (sgn((a[i + 1] - a[i]) * (a[r3 + 1] - a[r3])) < 0)
r3 = (r3 + 1) % n;
//! 1.更新
Line li = Line(a[i], a[i + 1]);
double area =
dispointtoline(a[r2], li) *
distance(progpointtoline(a[r1], li), progpointtoline(a[r3], li));
if (sgn(area - res) < 0) {
res = area;
vp.clear();
//! 3.操作点
vp.pb(progpointtoline(a[r1], li));
vp.pb(progpointtoline(a[r3], li));
Line lii = Line(a[r2], li.angle());
vp.pb(progpointtoline(a[r1], lii));
vp.pb(progpointtoline(a[r3], lii));
Point mi = vp[0];
for (auto i : vp) mi = min(mi, i);
sort(vp.begin(), vp.end(), cmp(mi));
}
}
//打印
printf("%.5lf\n", res);
for (auto i : vp) {
if (sgn(i.x - eps) <= 0) i.x = 0;
if (sgn(i.y - eps) <= 0) i.y = 0;
printf("%.5lf %.5lf\n", i.x, i.y);
}
//!返回值
return res;
}
signed main() {
cin >> n;
for (int i = 0; i < n; i++) a[i].input();
getconvex(a, n, convex);
getminrectanglecover(convex.p, convex.n);
return 0;
}
/*
input:::
6 1.0 3.00000
1 4.00000
2.0000 1
3 0.0000
3.00000 6
6.0 3.0
output:::
18.00000
3.00000 0.00000
6.00000 3.00000
3.00000 6.00000
0.00000 3.00000
*/
2.2最小周长外接矩形
3.三角剖分
3.1洋葱三角剖分
3.2螺旋三角剖分
3.3四边形剖分
4.凸多边形属性
4.1合并凸包
4.2找公切线
4.3凸多边形交
4.4临界切线
4.5凸多边形矢量和
5.最薄截面
5.1最薄横截带
博客中出现的所有题目
题目 | 问题类型 | 涉及知识 | 主要步骤 |
---|---|---|---|
P1452 [USACO03FALL]Beauty Contest G /【模板】旋转卡壳 | 求凸多边形直径 | 凸包,旋转卡壳 | 求凸包(先极角排序,然后再一个点一个点入栈) → \rightarrow →旋转卡壳(枚举边,卡每条边上的两个点对应的公共对踵点) |
P3187 [HNOI2007]最小矩形覆盖 | 求凸包最小覆盖矩形 | 凸包,旋转卡壳,以及各种计算几何的基本操作(点到直线投影,点到直线距离,过某点的平行线等等) | 先求凸包,然后卡三个点 |
J - 旋转卡壳 POJ - 3608 | 凸多边形间最短距离 | 凸包,旋转卡壳,线段与线段之间的距离等 | 先对两个凸包的点同时进行顺/逆时针排序,然后找到一个凸包的y坐标最小的点和另一个凸包的y坐标最大的点,然后枚举一个凸包的边,不断得到另一个点与该边所成叉积最大的点(注意,这里还有符号,叉积为正数的时候的值基本没啥用,叉积为负的时候我们才能得到最小距离值)。挺玄学,反正目前还没完全搞懂 |