计算几何初步

计算几何基础

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 <cstdio>
#include <cmath>
#include <vector>
#include <cstring>
#include <algorithm>
#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 <cstdio>
#include <cmath>
#include <vector>
#include <cstring>
#include <algorithm>
#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 <cstdio>
#include <cmath>
#include <vector>
#include <cstring>
#include <algorithm>
#include <queue>
#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_queue<data>q; 
    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; 
}

  

 

posted @ 2022-01-19 00:45  guangheli  阅读(41)  评论(0)    收藏  举报