几何问题
几何相关算法
向量命名空间
用pt
命令空间内的Point
类,实现基本的向量加减乘除运算,大小比较<
以及相等==
判断,内积dot
和外积cross
,向量长度length
,向量夹角angle
,向量旋转rotate
,以及一些求交点,判断是否正规相交,判断是否点在线段上,计算点到直线、线段距离的函数。
#include <cmath>
#include <string>
#include <cassert>
namespace pt { // 创建pt向量命名空间
const double EPS = 1e-10;
const double PI = std::acos(-1);
int sign(double x) { return std::fabs(x) < EPS ? 0 : (x < 0 ? -1 : 1); }
struct Point {
double x, y;
Point(double x=0, double y=0):x(x), y(y) {}
Point operator + (Point rhs) { return Point(x + rhs.x, y + rhs.y); }
Point operator - (Point rhs) { return Point(x - rhs.x, y - rhs.y); }
Point operator * (double rhs) { return Point(x * rhs, y * rhs); }
Point operator / (double rhs) { assert(sign(rhs) != 0); return Point(x / rhs, y / rhs); }
bool operator < (Point rhs) { return sign(x-rhs.x) == 0 ? sign(y-rhs.y) == -1 : x < rhs.x; } // 可用于排序去重
bool operator == (Point rhs) { return sign(x-rhs.x) == 0 && sign(y-rhs.y) == 0; }
double dot(Point rhs) { return x * rhs.x + y * rhs.y; }
double cross(Point rhs) { return x * rhs.y - y * rhs.x; }
void print(std::string s = "") { printf("%s(%.2lf, %.2lf)", s.c_str(), x, y); }
};
double dot(Point A, Point B) { return A.x * B.x + A.y * B.y; }
double cross(Point A, Point B) { return A.x * B.y - A.y * B.x; }
double length(Point A) { return std::sqrt(dot(A, A)); }
double angle(Point A, Point B) { return std::acos(dot(A, B) / length(A) / length(B)); }
Point rotate(Point A, double rad) { return Point(A.x*std::cos(rad)-A.y*std::sin(rad), A.x*std::sin(rad)+A.y*std::cos(rad)); }
Point line_intersection(Point A, Point u, Point B, Point v) { // 求直线A+tu和B+tv的交点
assert(sign(cross(u, v)) != 0);
Point w = A - B;
return A + u * cross(v, w) / cross(u, v);
}
bool segment_proper_intersection(Point A1, Point A2, Point B1, Point B2) { // 判断线段A1A2是否与B1B2正规相交(不包含端点相交)
double c1 = cross(A2-A1, B1-A1), c2 = cross(A2-A1, B2-A1);
double c3 = cross(B2-B1, A1-B1), c4 = cross(B2-B1, A2-B1);
return sign(c1*c2) == -1 && sign(c3*c4) == -1;
}
bool on_segment(Point P, Point A, Point B) { // 判断P在线段AB内部(不包含端点)
Point PA = A-P, PB = B-P;
return sign(dot(PA, PB)) == -1 && sign(cross(PA, PB)) == 0;
}
double distance2line(Point P, Point A, Point B) { return std::fabs(cross(B-A, P-A)) / length(B-A); } // 点P到直线AB的距离
double distance2segment(Point P, Point A, Point B) { // 点P到线段AB的距离
if (A == B) return length(P-A);
if (sign(dot(B-A, P-A)) == -1) return length(P-A);
if (sign(dot(A-B, P-B)) == -1) return length(P-B);
return distance2line(P, A, B);
}
double polygon_area(Point *P, int n) { // 计算多边形的有向面积(逆时针为定义为正向)
double area = 0;
for (int i = 1; i < n-1; i++) area += cross(P[i+1]-P[0], P[i]-P[0]);
return area / 2;
}
double convex_hull(Point *P, int n, Point *hull) { // 计算大小为n的点集P的凸包hull,返回凸包大小
std::sort(P, P+n); n = std::unique(P, P+n) - P; // 优先对x排序,再对y排序,并去重
int m = 0;
for (int i = 0; i < n; i++) { // 贪心求出下凸包
while (m > 1 && sign(cross(hull[m-1]-hull[m-2], P[i]-hull[m-2])) <= 0) m--;
hull[m++] = P[i];
}
int down = m;
for (int i = n-2; i >= 0; i--) { // 贪心求出上凸包,P[n-1]一定在下凸包中,不要重复枚举
while (m > down && sign(cross(hull[m-1]-hull[m-2], P[i]-hull[m-2])) <= 0) m--;
hull[m++] = P[i];
}
if (n > 1) m--; return m; // 如果n>=2则P[0]在上下凸包中重复出现,将其舍去
}
}
using namespace pt;
常用拓扑定理
平面上的Euler定理
设图 的顶点数、区域数(包括外部区域)、边数分别为 ,记 的信息为 ,则有 。
思路:利用到 的生成树 ,和 的对偶图 和与 对应的 上的对偶生成树 ,利用生成树的性质证明该定理。主要需要证明对偶图的存在唯一性(同构意义下),以及 满足树的定义(没有环且链接全部的 中全部顶点)。
证明:首先给出对偶图的定义,若 的信息满足 ,则可通过以下方法构造对偶图 的构造,且 的顶点数为 :
- 将 的每个区域作为 中的顶点。
- 对于 中的任意两个区域 ,如果 之间存在至少一个边,则在图 中连接 。
这样生成 的存在唯一性由上述构造方法可知。对于 中不属于 上的边 ,断言 一定划分了 的两个区域(反证法,利用 是树),也就是 在 中存在对应的边,全体满足此性质的边构成 。
下面证明 是树:
- 中无环,反证法,如果有环,则 一定不是树,因为由环围成的区域对应 中的点,该点一定不在 中,与 连接 中全部节点矛盾。
- 连接 的全部节点,如果节点 不在 中,则说明在 中围成区域 的边都在 中,这与 无环矛盾。
综上,我们得到 是 中的树。记 为 中的边数,则由 的构造方法可知 ,由树的性质可知 ,结合上面两式可知 。
QED
下图中,黑色边为图 ,红色边为 和 ,铅笔给出了图 。
几何问题
https://wty-yy.github.io/posts/54309/