计算几何
凸包
求凸包可以用 Graham 扫描法。
极角排序后,选一个坐标最小的点作极点,单调栈维护即可。
struct Point {
int x, y;
Point() {}
Point(int x, int y):x(x), y(y) {}
friend Point operator - (Point a, Point b) { return Point(a.x - b.x, a.y - b.y); }
friend int operator ^ (Point a, Point b) { return a.x * b.x + a.y * b.y; } // dot
friend int operator * (Point a, Point b) { return a.x * b.y - a.y * b.x); } // x
int norm() { return x * x + y * y; }
} p[N], q[N];
bool cmp (int a, int b) {
int det = (p[a] - p[1]) * (p[b] - p[1]);
if (det != 0) return det > 0;
return (p[a] - p[1]).norm() < (p[b] - p[1]).norm();
}
int pid[N], top;
void Graham() {
int id = 1;
for (int i = 2; i <= n; i++)
if (p[i].x < p[id].x || (p[i].x == p[id].x && p[i].y < p[id].y)) id = i;
id (id != 1) swap (p[1], p[id]);
for (int i = 1; i <= n; i++) pid[i] = i;
sort (pid + 2, pid + 1 + n, cmp);
q[++top] = p[1];
for (int i = 2; i <= n; i++) {
int j = pid[i];
while (top >= 2 && (p[j] - q[top - 1]) * (q[top] - q[top - 1]) >= 0) top--;
q[++top] = p[j];
}
}
旋转卡壳
对踵点对:如果凸包的顶点 $p,q$ 在两条平行切线,它们就形成了对踵点对。它有三种情况:点点、点边、边边。
以求凸包直径为例感受旋转卡壳。
求凸包直径方法一
一个直观的方法:
- 找到凸包纵坐标最小和最大两点 $A,B$ ;
- 构造两条水平切线 $y_1=y_A,y_2=y_B$ ,并先计算其距离更新答案;
- 同时旋转两条平行线直到其中一条与凸包上一边重合,产生新的点对,更新答案;
- 重复直到旋转一周。
求凸包直径方法二
以每条边为基础,计算距离最长的点。然后它是有单峰性的,而且上一条边用完可以给这条边接着用。 $\mathcal{O}(n)$
double rotateCalipers (Point *ch, int n) {
double ans = -INF;
ch[n + 1] = ch[1];
int p = 1;
for (int i = 1; i <= n; i++) {
ans = max({ans, (ch[p] - ch[i]).norm(), (ch[p] - ch[i + 1]).norm()});
while ((ch[p] - ch[i + 1]) * (ch[i] - ch[i + 1]) <= (ch[p + 1] - ch[i + 1]) * (ch[i] - ch[i + 1])) {
p = (p + 1) > n? 1: p + 1;
ans = max({ans, (ch[p] - ch[i]).norm(), (ch[p] - ch[i + 1]).norm()});
}
}
return ans;
}
类似的还有很多问题,咕咕咕咕。
半平面交
线性规划。
求凸壳,单调队列维护。
代码咕咕咕咕。
