计算几何初步
计算几何基础
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;
}
4. 线段和直线是否有交点
int seg_inter_line(Line l1, Line l2) {
if(dcmp(cross(l1.e - l2.s, l1.s - l2.s)) * dcmp(cross(l1.e - l2.e, l1.s - l2.e)) <= 0)
return 1;
return 0;
}
5. 直线和直线的交点
// l1 为直线,l2 为线段, 看是否相交
int seg_inter_line(Line l1, Line l2) {
if(dcmp(cross(l1.e - l2.s, l1.s - l2.s)) * dcmp(cross(l1.e - l2.e, l1.s - l2.e)) <= 0)
return 1;
return 0;
}
// 看直线 l1 和 l2 的位置情况.
int judge_Line(Line l1, Line l2) {
if(dcmp(cross(l1.e - l1.s, l2.e - l2.s)) == 0) {
// 0 : 共线,-1:平行.
if(seg_inter_line(l1, l2) == 1)
return 0;
else
return -1;
}
return 1;
}
// 求直线 l1 与 l2 的交点
point Get_Line(Line l1, Line l2) {
Vector v1 = l1.e - l1.s, v2 = l2.e - l2.s;
double t = cross(l2.s - l1.s, v2) / cross(v1, v2);
return l1.s + v1 * t;
}
6. 判断点是否在多边形内部
// 判断点 p 是否在多边形内.
// -1: 在边界.
// 0 : 在外部
// 1 : 在内部
int ispoly(point p, point poly[], int len) {
int cnt = 0;
Line ray, side;
ray.s = p;
ray.e = point(-1000000000.0, p.y);
for(int i = 0; i < len; ++ i) {
side = Line(poly[i], poly[(i + 1) % len]);
// 在边界
if(Onsegment(p, side.e, side.s))
return -1;
// 平行线不用考虑
if(dcmp(side.s.y - side.e.y) == 0)
continue;
if(Onsegment(side.s, ray.s, ray.e)) {
// 凸包点在射线上.
if(dcmp(side.s.y - side.e.y) > 0) ++ cnt;
}
else if(Onsegment(side.e, ray.s, ray.e)) {
if(dcmp(side.e.y - side.s.y) > 0) ++ cnt;
}
else if(Line_Intersect(side.e, side.s, ray.s, ray.e) == 1) ++ cnt;
}
if(cnt & 1) return 1;
return 0;
}
TOYS
来源:POJ - 2318
对于每个点,二分在最右侧的哪条线段的右侧,判断左右用叉积.
#include#include #include #include #include #define point Vector #define ll long long #define setIO(s) freopen(s".in","r",stdin) using namespace std; const double eps = 1e-8; struct Vector { double x, y; Vector(double X = 0.0, double Y = 0.0){ x = X, y = Y; } }; int dcmp(double x) { if(fabs(x) < eps) return 0; return x < 0 ? -1 : 1; } bool operator < (const point &a, const point & b) { if(dcmp(a.x - b.x) == 0) return a.y < b.y; else return a.x < b.x; } bool operator == (const point &a, const point & b) { if(dcmp(a.x - b.x) == 0 && dcmp(a.y - b.y) == 0) return true; return false; } Vector operator + (Vector a, Vector b) { return Vector(a.x + b.x, a.y + b.y); } Vector operator - (Vector a, Vector b) { return Vector(a.x - b.x, a.y - b.y); } Vector operator * (Vector a, double p) { return Vector(a.x * p, a.y * p); } Vector operator / (Vector a, double p) { return Vector(a.x / p, a.y / p); } double dot(Vector a, Vector b) { return a.x * b.x + a.y * b.y; } double len(point a) { return sqrt(dot(a, a)); } double sqr(double x) { return x * x; } double dis(point a, point b) { return sqrt(sqr(a.x - b.x) + sqr(a.y - b.y)); } // b 在 a 的逆时针为正,夹角为 a 转到 b 的有向角度(sin) double cross(Vector a, Vector b) { return a.x * b.y - a.y * b.x; } // 求点积再除以模长. double Angle(Vector a, Vector b) { return acos(dot(a, b) / len(a) / len(b)); } // 求法向量. Vector normal(Vector a) { double u = len(a); return Vector(-a.y / u, a.x / u); } // 看 tmp 是否在 ab 上 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; } // 前面是线段上,后面是不在线段上相交. 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; } #define N 20000 int n, m; struct Line { point a, b; }A[N]; point tp[N]; int fin[N]; int jud(int x, int i) { // 看第 x 线段. if(dcmp(cross(A[x].b - A[x].a, tp[i] - A[x].a)) == 1) return 1; return 0; } int main() { // setIO("input"); while(1) { scanf("%d", &n); if(n == 0) break; scanf("%d", &m); double x[2], y[2]; scanf("%lf%lf%lf%lf", &x[0], &y[0], &x[1], &y[1]); for(int i = 0; i <= n ; ++ i) fin[i] = 0; for(int i = 1; i <= n ; ++ i) { double u, l; scanf("%lf%lf", &u, &l); A[i].a = point(u, y[0]); A[i].b = point(l, y[1]); } for(int i = 1; i <= m ; ++ i) { scanf("%lf%lf", &tp[i].x, &tp[i].y); } for(int i = 1; i <= m ; ++ i) { int l = 1, r = n, an = 0; while(l <= r) { int mid = (l + r) >> 1; if(jud(mid, i)) an = mid, l = mid + 1; else r = mid - 1; } // an 的右侧,则 fin[an] ++ ; } for(int i = 0; i <= n ; ++ i) { printf("%d: %d\n", i, fin[i]); } printf("\n"); } return 0; }
Segments
来源:POJ - 3304
考虑通过公共投影点的线经过旋转后一定经过所有线段,即枚举该直线两个端点然后判断是否有交.
这里就要判断一条直线和线段是否有交点了.
#include#include #include #include #include #define point Vector #define ll long long #define setIO(s) freopen(s".in","r",stdin) using namespace std; const double eps = 1e-8; struct Vector { double x, y; Vector(double X = 0.0, double Y = 0.0){ x = X, y = Y; } }; struct Line { point s, e; Line() {}; Line(point i, point j) { s = i, e = j; } }; int dcmp(double x) { if(fabs(x) < eps) return 0; return x < 0 ? -1 : 1; } bool operator < (const point &a, const point & b) { if(dcmp(a.x - b.x) == 0) return a.y < b.y; else return a.x < b.x; } bool operator == (const point &a, const point & b) { if(dcmp(a.x - b.x) == 0 && dcmp(a.y - b.y) == 0) return true; return false; } Vector operator + (Vector a, Vector b) { return Vector(a.x + b.x, a.y + b.y); } Vector operator - (Vector a, Vector b) { return Vector(a.x - b.x, a.y - b.y); } Vector operator * (Vector a, double p) { return Vector(a.x * p, a.y * p); } Vector operator / (Vector a, double p) { return Vector(a.x / p, a.y / p); } double dot(Vector a, Vector b) { return a.x * b.x + a.y * b.y; } double len(point a) { return sqrt(dot(a, a)); } double sqr(double x) { return x * x; } double dis(point a, point b) { return sqrt(sqr(a.x - b.x) + sqr(a.y - b.y)); } // b 在 a 的逆时针为正,夹角为 a 转到 b 的有向角度(sin) double cross(Vector a, Vector b) { return a.x * b.y - a.y * b.x; } // 求点积再除以模长. double Angle(Vector a, Vector b) { return acos(dot(a, b) / len(a) / len(b)); } // 求法向量. Vector normal(Vector a) { double u = len(a); return Vector(-a.y / u, a.x / u); } // 看 tmp 是否在 ab 上 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; } // 前面是线段上,后面是不在线段上相交. 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; } // l1 为直线,l2 为线段 int seg_inter_line(Line l1, Line l2) { if(dcmp(cross(l1.e - l2.s, l1.s - l2.s)) * dcmp(cross(l1.e - l2.e, l1.s - l2.e)) <= 0) return 1; return 0; } #define N 3005 Line arr[N]; int n ; int check(point x, point y) { if(dcmp(dis(x, y)) == 0) return 0; Line now = Line(x, y); for(int i = 1; i <= n ; ++ i) { if(!seg_inter_line(now, arr[i])) return 0; } return 1; } void solve() { scanf("%d", &n); for(int i = 1; i <= n ; ++ i) { scanf("%lf%lf", &arr[i].s.x, &arr[i].s.y); scanf("%lf%lf", &arr[i].e.x, &arr[i].e.y); } int flag = 0; for(int i = 1; i <= n ; ++ i) { for(int j = i; j <= n ; ++ j) { if(check(arr[i].s, arr[j].s) || check(arr[i].s, arr[j].e) || check(arr[i].e, arr[j].s) || check(arr[i].e, arr[j].e)) flag = 1; } if(flag) break ; } if(flag) { printf("Yes!\n"); } else { printf("No!\n"); } } int main() { // setIO("input"); int T; scanf("%d", &T); while(T -- ) solve(); return 0; }
The Doors
来源:uva393
如果连边一定是在门的端点处连线.
所以问题就转化成判断所有线段是否会和一条线段相交,用模板判定后跑最短路即可.
这里注意为了严谨还要判断是否有两个线段重合的情况
#include#include #include #include #include #include #define point Vector #define ll long long #define setIO(s) freopen(s".in","r",stdin) using namespace std; const double eps = 1e-7; struct Vector { double x, y; Vector(double X = 0.0, double Y = 0.0){ x = X, y = Y; } }; struct Line { point s, e; Line() {}; Line(point i, point j) { s = i, e = j; } }; int dcmp(double x) { if(fabs(x) < eps) return 0; return x < 0 ? -1 : 1; } bool operator < (const point &a, const point & b) { if(dcmp(a.x - b.x) == 0) return a.y < b.y; else return a.x < b.x; } bool operator == (const point &a, const point & b) { if(dcmp(a.x - b.x) == 0 && dcmp(a.y - b.y) == 0) return true; return false; } Vector operator + (Vector a, Vector b) { return Vector(a.x + b.x, a.y + b.y); } Vector operator - (Vector a, Vector b) { return Vector(a.x - b.x, a.y - b.y); } Vector operator * (Vector a, double p) { return Vector(a.x * p, a.y * p); } Vector operator / (Vector a, double p) { return Vector(a.x / p, a.y / p); } double dot(Vector a, Vector b) { return a.x * b.x + a.y * b.y; } double len(point a) { return sqrt(dot(a, a)); } double sqr(double x) { return x * x; } double dis(point a, point b) { return sqrt(sqr(a.x - b.x) + sqr(a.y - b.y)); } // b 在 a 的逆时针为正,夹角为 a 转到 b 的有向角度(sin) double cross(Vector a, Vector b) { return a.x * b.y - a.y * b.x; } // 求点积再除以模长. double Angle(Vector a, Vector b) { return acos(dot(a, b) / len(a) / len(b)); } // 求法向量. Vector normal(Vector a) { double u = len(a); return Vector(-a.y / u, a.x / u); } // 看 tmp 是否在 ab 上 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; } // 前面是线段上,后面是不在线段上相交. 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)) { return 0; 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); if(f1 && f2) return 1; if(f3 && f4) return 1; return 0; } if(dcmp(x1) * dcmp(y1) < 0 && dcmp(x2) * dcmp(y2) < 0) return 1; return 0; } // l1 为直线,l2 为线段, 看是否相交 int seg_inter_line(Line l1, Line l2) { if(dcmp(cross(l1.e - l2.s, l1.s - l2.s)) * dcmp(cross(l1.e - l2.e, l1.s - l2.e)) <= 0) return 1; return 0; } // 看直线 l1 和 l2 的位置情况. int judge_Line(Line l1, Line l2) { if(dcmp(cross(l1.e - l1.s, l2.e - l2.s)) == 0) { // 0 : 共线,-1:平行. if(seg_inter_line(l1, l2) == 1) return 0; else return -1; } return 1; } // 求直线 l1 与 l2 的交点 point Get_Line(Line l1, Line l2) { Vector v1 = l1.e - l1.s, v2 = l2.e - l2.s; double t = cross(l2.s - l1.s, v2) / cross(v1, v2); return l1.s + v1 * t; } #define N 2004 struct Dij { int hd[N], to[N << 1], nex[N << 1], vis[N], edges; double val[N << 1], d[N]; struct data { int x; double dis; data(int x = 0, double dis = 0):x(x),dis(dis) {} bool operator<(const data b) const { return dis > b.dis; } }; priority_queueq; void add(int u, int v, double c) { nex[++ edges] = hd[u], hd[u] = edges, to[edges] = v; val[edges] = c; } void dijkstra(int s, int tot) { for(int i = 0; i <= tot; ++ i) d[i] = 1000000000, vis[i] = 0; d[s] = 0; q.push(data(s, 0)); while(!q.empty()) { data e = q.top(); q.pop(); if(vis[e.x]) continue; vis[e.x] = 1; for(int i = hd[e.x]; i ; i = nex[i]) { int v = to[i]; if(d[v] > d[e.x] + val[i]) { d[v] = d[e.x] + val[i]; q.push(data(v, d[v])); } } } } void clr(int tot) { for(int i = 0; i <= tot; ++ i) { vis[i] = d[i] = 0; } for(int i = 0; i <= edges; ++ i) { nex[i] = to[i] = val[i] = 0; } for(int i = 0; i <= tot; ++ i) { hd[i] = 0; } edges = 0; } }T; Line a[N]; point b[N]; int cnt, tot, n; void add_line(point x, point y) { a[++ tot] = Line(x, y); } int judge(Line tmp) { // 看 tmp 是否能不碰任何线段. if(dcmp(dis(tmp.s, tmp.e)) == 0) return 0; for(int i = 1; i <= tot ; ++ i) { if(Line_Intersect(tmp.s, tmp.e, a[i].s, a[i].e) == 1) return 0; } return 1; } int main() { // setIO("input"); // freopen("input.out","w",stdout); // int fl = 0; while(1) { scanf("%d", &n); if(n == -1) { break; } cnt = tot = 0; b[++ cnt] = point(0, 5); b[++ cnt] = point(10, 5); for(int i = 1; i <= n ; ++ i) { double x, y[4]; scanf("%lf%lf%lf%lf%lf", &x, &y[0], &y[1], &y[2], &y[3]); add_line(point(x, 0), point(x, y[0])); add_line(point(x, y[1]), point(x, y[2])); add_line(point(x, y[3]), point(x, 10)); b[++ cnt] = point(x, y[0]); b[++ cnt] = point(x, y[1]); b[++ cnt] = point(x, y[2]); b[++ cnt] = point(x, y[3]); } // s = 1, t = 2 for(int i = 1; i <= cnt; ++ i) { for(int j = i + 1; j <= cnt; ++ j) { if(judge(Line(b[i], b[j]))) { T.add(i, j, dis(b[i], b[j])); T.add(j, i, dis(b[i], b[j])); } } } T.dijkstra(1, cnt); printf("%.2f\n", T.d[2]); T.clr(cnt); } return 0; }