感觉写这个总是怀疑自己数学是不是完蛋了
以下全为抄课件
code
struct Point;
typedef Point Vector;
struct Point{
double x, y;
void in() {
scanf("%lf%lf", &x, &y);
}
void print() {
printf("%.2lf %.2lf
", x, y);
}
Point(double x = 0, double y = 0) : x(x), y(y) {
}
//坐标旋转
inline Vector rotate(double ang) {
return Vector(x * cos(ang) - y * sin(ang), x * sin(ang) + y * cos(ang));
}
inline double dot(const Vector &a) {
return x * a.x + y * a.y;
}
inline bool operator == (const Point &a) const {
return sgn(x - a.x) == 0 && sgn(y - a.y) == 0;
}
inline bool operator < (const Point &a) const {
return sgn(x - a.x) < 0 || sgn(x - a.x) == 0 && sgn(y - a.y) < 0;
}
inline Vector operator + (const Vector &a) const {
return Vector(x + a.x, y + a.y);
}
inline Vector operator - (const Vector &a) const {
return Vector(x - a.x, y - a.y);
}
//叉积
inline double operator * (const Vector &a) const {
return x * a.y - y * a.x;
}
inline Vector operator * (double t) const {
return Vector(x * t, y * t);
}
inline Vector operator / (double t) {
return Vector(x / t, y / t);
}
inline double vlen() {
return sqrt(x * x + y * y);
}
inline Vector norm() {
return Point(-y, x);
}
};
//直线相交
point intersect(point P, Vector v, point Q, Vector w) {
point p;
Vector u = P - Q;
double t = (w * u) / (v * w);
p = P + v * t;
return p;
}
//凸包
void gao(){
if(n < 3){
return;
}
std::sort(p, p + n);
std::copy(p, p + n - 1, p + n);
std::reverse(p + n, p + 2 * n - 1);
int m = 0, top = 0;
for(int i = 0; i < 2 * n - 1; i++) {
while(top >= m + 2 && sgn((p[top - 1] - p[top - 2]) * (p[i] - p[top - 2])) <= 0) {
top--;
}
p[top++] = p[i];
if(i == n - 1) {
m = top - 1;
}
}
n = top - 1;
}
//半平面交
int inhalfplane(point p,point s,point e) {
return sgn((e - s) * (p - s)) ;
}
std::vector<point> CUT(const std::vector<point> &p, point s, point e) {
//p: 当前维护的凸包,s: 加入直线的起点, e: 加入直线的终点
//可用的一侧平面在直线左侧。
//分类讨论可推导出。
std::vector<point> q;
int n = (int) p.size();
for(int i = 0; i < n; i++) {
int nowin = inhalfplane(p[i], s, e);
int nextin = inhalfplane(p[(i + 1) % n], s, e);
if(nowin >= 0) {
q.push_back(p[i]);
}
if(nextin * nowin < 0) {
q.push_back(intersect(p[i], p[(i + 1) % n] - p[i], s, e - s));
}
}
return q;
}
//多边形判断是否在内部
bool point_in_polygon(Point o, Point *p, int n) {
int t;
Point a, b;
p[n] = p[0];
for(int i = 0; i < n; i++) {
if(dot_on_seg(o, Seg(p[i], p[i + 1]))) {
return true;
}
}
t = 0;
for(int i = 0; i < n; i++) {
a = p[i]; b = p[i + 1];
if(a.y > b.y) {
std::swap(a, b);
}
if(sgn((a - o) * (b - o)) < 0 && sgn(a.y - o.y) < 0 && sgn(o.y - b.y) <= 0) {
t++;
}
}
return t & 1;
}
基础知识:向量,点积,叉积,直线(线段)相交
向量
点 (A(x_1,y_1)) 指向 (B(x_2,y_2)) 的向量为 ((x_2-x_1,y_2-y_1))
一个向量可以表示两个点之间的方向,以及长度
点积(内积)
两个向量 (A,B) 做点积等价于 (x_1*x_2+y_1*y_2=|A||B|*cos( heta))
点积也用来求两个向量的夹角
还可以用来求一个向量在另一个向量上的投影
叉积(外积)
两个向量 (A(x_1,y_1), B(x_2,y_2)) 的叉积可以表示为 (x_1*y_2-x_2*y_1)
叉积的值如果为正,表示从B向量在A向量的左边;如果为负,表示B向量在A向量的右边。
即假设A,B向量都从原点O出发,那么 从OA到OB如果在顺时针180度范围内,叉积就为正,否则为负
直线相交
求两直线交点需要先判断是否平行或者共线
求交点时可以利用参数方程来做
将第一条直线上的点表示成 ((P.x+v.x * t_1, P.y + v.y * t_1)) 的形式
将第二条直线上的点表示成 ((Q.x+w.x*t_2,Q.y+w.y * t_2)) 的形式
两项分别相等可以解出 (t_1) 和 (t_2),就可以求出交点
求线段交点可以先求直线交点 再判断交点是否在线段上
坐标旋转
点 ((x_1, y_1)) 以 ((x_0, y_0)) 为中心逆时针旋转 (a) 度
等价于 ((x_1-x_0, y_1-y_0)) 绕原点旋转,求出旋转后的坐标加上 ((x_0,y_0)) 即可
((x,y)) 绕原点逆时针旋转 (a) 度会得到
((x*cos(a) - y * sin(a), x*sin(a) + y * cos(a)))
凸包与半平面交
凸包
常见求凸包的方法有两种:水平序和极角序
水平序是先求下凸折线,再求上凸折线,最后拼成凸包
极角序是选择一个左下角的点当作原点,所有的点按照 与原点连线后跟x轴正向的角度 排序(此处可以学一下atan2函数)
排序之后的操作都差不多,下面以水平序为例
凸包的思想也经常用于其它与单调性有关的算法中,比如斜率优化
将凸包分成上下两条凸折线
用一个栈记录折线上的点,假设栈顶的元素为b,前一个为a
假设当前点为c
如果 (cross(a, b, c) ge 0) 则直接将c加入栈中
否则将b退栈,直到 (cross(a,b,c) ge 0)
半平面交
有若干个 (ax+by+c = 0) 的直线, 每个直线有一侧是我们需要的一个半平面,求所有半平面的交集(一个凸多边形)
有 (n^2) 与 (nlog n) 两种做法
此处介绍 (n^2) 做法
维护一个凸包,表示半平面的交,
每次新来一条直线,暴力找到这条直线会与哪条凸包上的边相交(如果某条边两个端点在直线的异侧,肯定有交点)
然后求出新的交点,维护新的凸包
(nlog n) 的算法使用双端队列优化,可参考刘汝佳训练指南
其他:三角形,多边形,坐标旋转
多边形
求多边形的面积(不一定是凸的):随便按照一个方向求相邻 三个点构成的有向三角形的面积之和, 最后取绝对值
(O(log n)) 判断点是否在凸多边形内部:二分判断点在哪一块三角形区域内,最后再判断点是否在三角形内部
判断点在任意⼀个多边形内部:射线法,做一条线,判断与多边形相交几次,奇数次就表示在多边形内部,否则在外部