旋转卡壳&凸包(不用一下子就学完所有)

前言

  1. 不用总想着一下子就学完所有知识,刷题要紧(能遇到的东西才是重点!!!
  2. 刷的越多,才越知道考什么,怎么考。
  3. 只是三个问题最重要,最常用:
    • 凸多边形直径
    • 最小面积外接矩形
    • 凸多边形见最小距离
  4. 旋转卡壳解题关键:怎么卡,其他就是套模板了(其实模板内容也不过是一些数学常识)

参考博客

1.旋转卡壳解决一类问题(资料)

  • 好极了,甚至让我有了想要好好学习的冲动。
  • 本博文任何知识都可以优先参考该博客

前置知识

1.极角排序

  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;
    }
};
  1. 为什么上面那种不对,还是后面再来思考吧:破案了,原来是因为我在函数里面又新加了Point mi;emmm就变成了局部变量。

2.凸包(默认逆时针)

  1. 参考博客:数学:凸包算法详解
  2. 定义:凸的多边形,包含所有给定点
  3. 解法: 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,...,pn1
      3)排序之后先将 p 1 p_1 p1放入栈顶,然后从 p 2 p_2 p2一直更新到 p n − 1 p_{n-1} pn1为止( p n − 1 p_{n-1} pn1一定在凸包上!!!)。
      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,...,pn1
    • 排序思路2(直角坐标排序——求上下凸包的时候用,如果想在求凸包中用,就要先求上、下凸包 ,然后再拼凑起来(而且还有要注意的东西——点重了或者少了)。距离说明:长宽都平行于坐标轴的正方形): p 0 p_0 p0为最左边的点,然后按 x x x坐标从小到大排序, x x x相等时 y y y坐标从大到小排序
  4. 模板
//由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;
}
  1. 另一种求上下凸壳的方法感觉不是很必要。不用学就不学,多花些时间思考这些知识怎么应用才是王道(保持对题目的敏感)。

3.对踵点

  1. 参考博客:对踵点对
  2. 切线:在这里插入图片描述
  3. 对踵点对:(平行切线上的两点)在这里插入图片描述
  4. (凸包)三种情况:
    在这里插入图片描述

旋转卡壳能解决的各类问题

能解决的问题:(学基础的,然后剩下的遇到再补)

1.计算距离

1.1凸多边形直径

  1. 参考博客:旋转卡壳——凸多边形直径
  2. 定义:多边形上任意两点间的距离的最大值
  3. 题解:求出凸包之后,直接遍历每条边,求每条边的两个点共有的对踵点(可能1个,可能两个,不知道这里为什么只需要考虑一个,即continue后面的都不需要)。
  4. 模板:
    • 注意!!!。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;
}
  1. 模板题: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 2n50000x,y104
    • 题解:旋转卡壳模板题,不解释
    • 代码:
#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. 定义:凸多边形的任意两条平行切线间的最短距离。
  2. 题解:和求凸多边形长几乎一样,但是这里不关注“点-点对踵点”,只关注“点-对对踵点”的最小值。需要用到点到直线的距离公式
  3. 模板:先略。遇到再说,每遇到就先理解。

1.3凸多边形间最大距离

1.4凸多边形间最小距离

  1. 题目:J - 旋转卡壳 POJ - 3608
  2. 题解见:Bridge Across Islands POJ - 3608(旋转卡壳+凸多边形间最短距离)
  3. 模板:
// !注意要先对两凸包的点数组进行极角排序(同时顺时针,同时逆时针都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最小面积外接矩形

  1. 要求:求凸包的所有外接矩形种的最小值。(又是要求输出矩形各点)
  2. 解法:(如果不是逆时针凸包就先求凸包),枚举边,卡三个点(上面求长和宽是卡一个点。至于怎么卡,建议自己先想想)
  3. 具体的见博客:P3187 [HNOI2007]最小矩形覆盖(凸包,旋转卡壳,凸包最小矩形覆盖–求面积及各点)
  4. 代码:
//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坐标最大的点,然后枚举一个凸包的边,不断得到另一个点与该边所成叉积最大的点(注意,这里还有符号,叉积为正数的时候的值基本没啥用,叉积为负的时候我们才能得到最小距离值)。挺玄学,反正目前还没完全搞懂
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值