计算几何基础
1. 判断点是否在线段上
叉积必为 0 保证在延长线上,点积不大于 0 保证不会在线段的两侧.
int Onsegment(point tmp, point a, point b) { if(dcmp(cross(a - tmp, b - tmp)) == 0 && dcmp(dot(a - tmp, b - tmp)) <= 0) return 1; return 0; }
2. 判断两个线段是否有相交(不在端点处)
看 $\mathrm{(a,b)}$ 和 $\mathrm{(c,d)}$ 分别都在各自两侧.
// 前面是线段上,后面是不在线段上相交. int Line_Intersect(point a, point b, point c, point d) { double x1 = cross(b - a, c - a), y1 = cross(b - a, d - a); double x2 = cross(d - c, a - c), y2 = cross(d - c, b - c); if(!dcmp(x1) || !dcmp(x2) || !dcmp(y1) || !dcmp(y2)) { bool f1 = Onsegment(a, c, d); bool f2 = Onsegment(b, c, d); bool f3 = Onsegment(c, a, b); bool f4 = Onsegment(d, a, b); bool f = (f1 | f2 | f3 | f4); return f; } if(dcmp(x1) * dcmp(y1) < 0 && dcmp(x2) * dcmp(y2) < 0) return 1; return 0; }
3. 凸包
$\mathrm{Graham}$ 扫描法:
将 $\mathrm{y}$ 坐标最小的点作为基准给其他点按照角度逆时针排序.
加入新点的时候看之前加的边是否在新加的边的左侧,如果是则弹掉.
// b 在 a 的逆时针为正,夹角为 a 转到 b 的有向角度(sin) double cross(Vector a, Vector b) { return a.x * b.y - a.y * b.x; } int n ; point p[N], A[N]; bool cmp2(point i, point j) { int t = dcmp(cross(i - p[1], j - p[1])); if(t != 0) return t > 0; else { return dis(i, p[1]) < dis(j, p[1]); } } int main() { // setIO("input"); scanf("%d", &n); for(int i = 1; i <= n ; ++ i) { scanf("%lf%lf", &p[i].x, &p[i].y); // if(p[i].y < p[1].y) swap(p[i], p[1]); } sort(p + 1, p + 1 + n); sort(p + 2, p + 1 + n, cmp2); int cnt = 1; A[1] = p[1]; for(int i = 2; i <= n ; ++ i) { while(cnt > 1 && dcmp(cross(A[cnt] - A[cnt - 1], p[i] - A[cnt])) != 1) -- cnt ; A[++ cnt] = p[i]; } A[++ cnt] = p[1]; double ans = 0.0; for(int i = 1; i < cnt ; ++ i) ans += dis(A[i], A[i + 1]); printf("%.2f", ans); return 0; }