计算几何基础

精度设置:eps判0

double const EPS = 1E-6;
#define is0(x) ( -EPS <= (x) && (x) <= EPS )

整数表示点坐标:

//点
struct point_t{
    int x;
    int y;
};

点积

  • 点积隐含了两个向量夹角的余弦值。
  • 点积为0,两向量垂直;为正,锐角;为负,钝角。仿照叉积

点积的实现如下:

//向量的点积,表示OA·OB
int dot(point_t const&O,point_t const& A,point_t const& B){
    int xoa = A.x – O.x;
    int yoa = A.y – O.y;
    int xob = B.x – O.x;
    int yob = B.y – O.y;
    return xoa * xob + yoa * yob;
}

叉积

  • 二维叉积代表2个向量张成的平行四边形的“有向”面积
  • 向量a与向量b的叉积为正,则a到b是逆时针方向;反之则为顺时针。

使用3个点计算叉积

//向量的叉积,表示OA×OB
int cross(point_t const&O,point_t const&A,point_t const&B)
{
    int xoa=A.x-O.x;
    int yoa=A.y-O.y;
    int xob=B.x-O.x;
    int yob=B.y-O.y;
    return xoa*yob-yoa*xob;
}

 叉积判断两个向量的幅角大小

  • 求向量a(x,y)的幅角可以调用atan(y,x),(浮点数有精度误差)还可用叉积算
  • 首先判断向量a,b所在象限,不在同一象限则大小很明显,否则计算它们的叉积,由正负可以判断大小。

判断两线段是否相交

线段数据结构:

//直线SE
struct lineseg_t{
    point_t s;
    point_t e;
};

 判断两线段AB与CD是否相交,两步:

1、排斥实验:即存在一条直线l,AB与CD在其上的投影不相交,则它们一定不相交。但不满足排斥实验不一定相交。

可以将y轴作为l,则比较两条线段最大的x即可。

2、跨立实验:若两条直线相交,则有CD跨过了AB,且AB跨过CD。即CD在AB的两侧,AB在CD的两侧。

下图:判断CD跨过了AB,即CD在AB的两侧:即AD到AB方向与AB到AC方向,即AD×AB与AB×AC同号

判断AB没跨过CD:即CA×CD与CD×CB不同号,则不想交

//判断线段AB与CD是否相交,true表示相交
bool isInter(point_t const&A,point_t const&B, point_t const&C,point_t const&D)
{
    return max(A.x, B.x) >= min(C.x, D.x) //排斥
         && max(A.y, B.y) >= min(C.y, D.y) //排斥
         && max(C.x, D.x) >= min(A.x, B.x) //排斥
         && max(C.y, D.y) >= min(A.y, B.y) //排斥
         && cross(A, C, B) * cross(A, B, D) >= 0 //跨立
         && cross(C, A, D) * cross(C, D, B) >= 0 ; //跨立
}

//判断直线AB与线段CD是否相交
bool isIntersect(point_t const&A,point_t const&B, point_t const&C,point_t const&D)
{
    return cross(A,B,C)*cross(A,B,D)>eps;//true表示不相交
}

直线的位置关系(平面几何)

所用直线数据结构:

//直线ax+by+c=0;
struct line_t
{
    int a,b,c;//表示ax+by+c=0,一般使得a、b、c互质
}

题目一般不会给直线方程,而是给定两个点,所以可以用叉积计算出a,b,c。

a = y1 - y2,  b = x2 - x1,  c = x1y2 - x2y1

求出两条直线的方程后,通过a,b,c的关系就很容易判断直线关系了。

令:x = b1c2 - b2c1,  y = a2c1 - a1c2,  t = a1b2 - a2b1,则:

重合:x=y=t=0

平行:t=0

否则相交:两直线相交且交点为(x/t, y/t)

实例

叉积:poj2318:TOYS

theme:给定一个大箱子的左上、右下坐标、隔板数、玩具数。给出n个隔板的横坐标,(两个纵坐标同隔板),保证不会相交,且按从左到右顺序给出。给出m个玩具的横纵坐标,问最终每个区域的玩具数。0 <n, m <= 5000

solution:对于每一个点,枚举每一个区域判断它是否在其内部即可。每个区域由相邻两条直线组成,所以只需要写个函数判断点O是否在线段AB、CD(及上下边界)围成的区域里,包括左右边界。

//判断点O是否在线段AB、CD(及上下边界)围成的区域里,包括左右边界
#include<iostream>
#include<cstdio>
#include<algorithm>
typedef long long LL;
#define inf 0x3f3f3f3f
using namespace std;
#define far(i,s,n) for(int i=s;i<n;++i)

//点
struct point_t{
    int x;
    int y;
};
//叉积
int cross(point_t const&O,point_t const&A,point_t const&B)
{
    int xoa=A.x-O.x;
    int yoa=A.y-O.y;
    int xob=B.x-O.x;
    int yob=B.y-O.y;
    return xoa*yob-yoa*xob;
}
//判断点O是否在线段AB、CD(及上下边界)围成的区域里,包括左右边界
bool inside(point_t const&O,point_t const& A,point_t const& B,point_t const& C,point_t const& D)
{
    if(O.x<min(A.x,B.x)||O.x>max(C.x,D.x))return false;
    if(O.y<B.y||O.y>A.y)return false;
    int crossl=cross(B,O,A);
    int crossr=cross(D,C,O);
    return (crossl>=0)&&(crossr>=0);
}

int n,m,xlm,ylm,xrm,yrm;
int U[5050],L[5050];
int cnt[5050];

int main()
{
    while(scanf("%d",&n)&&n)
    {
        scanf("%d%d%d%d%d",&m,&xlm,&ylm,&xrm,&yrm);
        fill(cnt,cnt+n+2,0);
        far(i,1,n+1)
            scanf("%d%d",&U[i],&L[i]);
        far(i,0,m)
        {
            point_t toy;
            scanf("%d%d",&toy.x,&toy.y);
            //判断是否在0号区域
            if(inside(toy,point_t{xlm,ylm},point_t{xlm,yrm},point_t{U[1],ylm},point_t{L[1],yrm}))
                ++cnt[0];
            else if(inside(toy,point_t{U[n],ylm},point_t{L[n],yrm},point_t{xrm,ylm},point_t{xrm,yrm}))
                ++cnt[n];
            else
            {
                far(j,1,n)
                {
                    if(inside(toy,point_t{U[j],ylm},point_t{L[j],yrm},point_t{U[j+1],ylm},point_t{L[j+1],yrm}))
                    {
                        ++cnt[j];
                        break;
                    }
                }
            }
        }
        far(i,0,n+1)
            printf("%d: %d\n",i,cnt[i]);
        puts("");
    }
}

叉积:poj3304:Segments

theme:给定n条线段,问是否存在一条直线,使得所有n条直线投影到该直线的区域有公共点。n ≤ 100

solution:如果存在这样一条直线,则过公共点坐该直线的垂线一定与所有n条线段相交,所以问题转化为如果有一条直线与所有线段相交,则与该直线垂直的直线即为所求。所以我们的目标是判断欲呕恶u一条直线与所有线段相交。首先如果存在,则经过n条直线2n个顶点任意两点的直线一定是(平移这条直线一定可以一到与至少两个顶点相交,否则这条直线也不会和所有直线相交。)所以我们只需枚举2n个顶点即可,对于只有1条线段等的情况,可能就是该直线与所有直线相交,所以一条线段的两个端点也要考虑。注意数据可能出现十分相近的点,这时应该跳过

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;

double eps=1e-8;

//点
struct point_t
{
    double x;
    double y;
};
//叉积
double cross(point_t const&O,point_t const&A,point_t const&B)
{
    double xoa=A.x-O.x;
    double yoa=A.y-O.y;
    double xob=B.x-O.x;
    double yob=B.y-O.y;
    return xoa*yob-yoa*xob;
}

//判断直线AB与CD是否相交,true表示不相交
bool isIntersect(point_t const&A,point_t const&B, point_t const&C,point_t const&D){
    return cross(A,B,C)*cross(A,B,D)>eps;
}

double X[220],Y[220];
int n;

bool check(point_t A,point_t B)
{
    if(fabs(A.x-B.x)<eps&&fabs(A.y-B.y)<eps)
        return false;
    for(int i=1;i<=n;++i)
    {
        point_t C=point_t{X[i],Y[i]};
        point_t D=point_t{X[i+n],Y[i+n]};
        if(isIntersect(A,B,C,D))
            return false;
    }
    return true;
}

int main()
{
    int Case;
    cin>>Case;
    while(Case--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
            scanf("%lf%lf%lf%lf",&X[i],&Y[i],&X[i+n],&Y[i+n]);
        int flag=0;
        for(int i=1;i<=2*n;++i)
        {
            if(flag)
                break;
            for(int j=i+1;j<=2*n;++j)
            {
                point_t A=point_t{X[i],Y[i]};
                point_t B=point_t{X[j],Y[j]};
                if(check(A,B))
                {
                    flag=1;
                    break;
                }
            }
        }
        if(flag)
            puts("Yes!");
        else
            puts("No!");
    }
}

直线位置关系:poj1269:Intersecting Lines

theme:判断两条直线的位置关系

solution:求出两条直线的a,b,c

//theme:判断两条直线的位置关系
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;

//直线ax+by+c=0;
struct line_t
{
    int a,b,c;//表示ax+by+c=0,一般使得a、b、c互质
};

int check(line_t l1,line_t l2,double &ix,double &iy)
{
    int x=l1.b*l2.c-l2.b*l1.c;
    int y=l2.a*l1.c-l1.a*l2.c;
    int t=l1.a*l2.b-l2.a*l1.b;
    if(x==0&&y==0&&t==0)
        return 1;
    if(t==0)
        return 2;
    ix=1.0*x/t;
    iy=1.0*y/t;
    return 3;
}

int main()
{
    int n;
    cin>>n;
    puts("INTERSECTING LINES OUTPUT");
    while(n--)
    {
        int x1,x2,y1,y2;
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        line_t l1=line_t{y1-y2,x2-x1,x1*y2-x2*y1};
        int x3,x4,y3,y4;
        scanf("%d%d%d%d",&x3,&y3,&x4,&y4);
        line_t l2=line_t{y3-y4,x4-x3,x3*y4-x4*y3};
        double x=0,y=0;
        int res=check(l1,l2,x,y);
        if(res==1)
            puts("LINE");
        else if(res==2)
            puts("NONE");
        else
            printf("POINT %.2f %.2f\n",x,y);
    }
    puts("END OF OUTPUT");
}

Pick面积公式

  • 平面上以格子点为顶点的简单多边形的面积=边界覆盖点数/2 + 内部覆盖点数 -1
  • 以格子点为顶点的线段,覆盖的点的个数为gcd(dx,dy),其中,dx、dy分别为线段横向占的点数和纵向占的点数。如果dx或dy为0,则覆盖的点数为dy或dx。
  • 顶点是整数就暗示在网格点上,一般面积都是用叉积算,再用pick公式求出内部覆盖点数

将平面中的点按逆时针排序

即按叉积排序

#include <iostream> 
#include <cmath>
#include <algorithm>
using namespace std;
struct Vector {
	double x, y;
	Vector(int x, int y):x(x), y(y){}
	Vector(){}
	double operator ^ (const Vector &v) const {
		return x * v.y - y * v.x; //叉乘 
	}
};
#define Point Vector
Vector operator - (const Point &p1, const Point& p2) {
	return Vector(p1.x - p2.x, p1.y - p2.y);
}
bool operator < (const Point &p1, const Point &p2) {
	return (Vector(p2 - Point(0, 0)) ^ Vector(p1 - Point(0, 0))) > 0;
} 
Point ps[60];
int main()
{
	int n(0);
	while(cin >> ps[n].x >> ps[n].y) {
		++n;
	}
	sort(ps + 1, ps + n);//以p0作为第一个点逆时针排序
	cout << "(0,0)" << endl;
	for (int i = n - 1; i > 0; --i) {
		cout << "(" << ps[i].x << "," << ps[i].y << ")" << endl;
	}
	
	return 0;
}

poj1265:Area

theme:逆时针给出多边形m条边沿x、y轴方向的长度,多边形的顶点都在网格上,求多边形边所覆盖的网格点个数、内部覆盖的网格点个数、多边形的面积。

solution:看到顶点在网格上,求多边形面积就应该想到pick公式,求多边形面积可以用叉积

求出面积后用gcd(dx,dy)可以求出边所覆盖的网格点个数,再用pick公式就可以求出内部覆盖的点的个数。

//theme:逆时针给出多边形m条边沿x、y轴方向的长度,多边形的顶点都在网格上,求多边形边所覆盖的网格点个数、内部覆盖的网格点个数、多边形的面积。
/*
注意这题因为给的是边的距离,我们自己假设第一个点为起点,所以求面积的时候就忽略了最后一个点与第一个点的叉积*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#define far(i,s,t) for(int i=s;i<t;++i)
using namespace std;

int gcd(int a,int b)
{
    return b==0?a:gcd(b,a%b);
}

int main()
{
    int Case;
    cin>>Case;
    int now=0;
    while(Case--)
    {
        ++now;
        int n;
        scanf("%d",&n);
        int dx,dy,area=0,inside,edge=0;
        int x1=0,y1=0,x2,y2;
        far(i,0,n)//起点假设为原点就没必要算起点与最后一个点的叉积了
        {
            scanf("%d%d",&dx,&dy);
            edge+=abs(gcd(dx,dy));
            x2=x1+dx,y2=y1+dy;
            area+=x1*y2-x2*y1;
            x1=x2,y1=y2;
        }
        if(area<0)
            area=-area;
        inside=(area-edge+2)/2;
        printf("Scenario #%d:\n%d %d %.1f\n\n",now,inside,edge,area/2.0);
    }
}

任意多边形面积求法

任意一个多边形的面积=按顺序求相邻两个点与原点组成的向量的叉积之和/2。由于叉积是有方向的,所以最终结果取绝对值即可

其实就是拆分成多个三角形求和再减,与源点坐标的选取无关.

 注意最后还要算最后一个点与第一个点的叉积

image     imageimage

给定n个单位圆,求一条直线最多能穿过多少个圆?

//给定n个单位圆,求一条直线最多能穿过多少个圆?
#include <iostream>
#include <cmath>
double const PI = acos(-1);
using namespace std;
struct Point{
	double x,y;
};
typedef Point Vector;
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};
}
bool operator < (const Point& a,const Point& b){
	return a.x<b.x||(a.x==b.x&&a.y<b.y);
}
struct Line{
	Vector v;
	Point p;
};
struct Circle{
	Point c;
	double r;
	Point getPoint(double a){
		return Point{c.x+cos(a)*r,c.y+sin(a)*r};
	}

};
const double eps = 1e-6;
int dcmp(double x){
	if(fabs(x)<eps) return 0;
	else return x<0?-1:1;
}
bool operator == (const Point& a,const Point& b){
	return dcmp(a.x-b.x)==0&&dcmp(a.y-b.y)==0;
}

int getLineCircleIntersection(Line L,Circle C){
	double a = L.v.x,b=L.p.x - C.c.x,c=L.v.y,d=L.p.y-C.c.y;
	double e=a*a + c*c,f=2*(a*b+c*d),g=b*b+d*d-C.r*C.r;
	double delta = f*f - 4*e*g;
	if(dcmp(delta)<0) return 0;//相离
	if(dcmp(delta)==0) return 1;//相切
	return 2;//相交
}

int getTangents(Circle A, Circle B, Point *a, Point *b){
	if (A.c==B.c){
		a[0]=A.c;
		b[0]=A.getPoint(0);
		return 1;
	}
	int cnt = 0;
	double d2 = (A.c.x-B.c.x)*(A.c.x-B.c.x) + (A.c.y-B.c.y)*(A.c.y-B.c.y);
	double rDiff = 0.0;
	double rSum = 2.0;

	double base = atan2(B.c.y - A.c.y, B.c.x - A.c.x);

	double ang = acos(0);
	a[cnt] = A.getPoint(base + ang);
	b[cnt] = B.getPoint(base + ang);
	++cnt;
	a[cnt] = A.getPoint(base - ang);
	b[cnt] = B.getPoint(base - ang);
	++cnt;
	if(dcmp(d2 - 4.0) == 0){
		a[cnt] = A.getPoint(base);
		Vector t_v = A.c-B.c;
		if(dcmp(t_v.y)==0){
			b[cnt]= Point{a[cnt].x,a[cnt].y+1.0};
		}
		else{
			b[cnt] = Point{a[cnt].x+1.0,a[cnt].y-t_v.x/t_v.y};
		}
		++cnt;
	}
	else if(dcmp(d2 - 4.0)>0){
		double ang2 = acos((2.0)/sqrt(d2));
		a[cnt] = A.getPoint(base + ang2);b[cnt] = B.getPoint(PI+base+ang2);++cnt;
		a[cnt] = A.getPoint(base - ang2);b[cnt] = B.getPoint(PI+base-ang2);++cnt;
	}
	return cnt;
}

const int maxn = 105;
int n;
Point poi[maxn];
Circle cir[maxn];
int main(){
	cin>>n;
	double x,y;
	for(int i=1;i<=n;i++){
		cin>>x>>y;
		poi[i].x=x;
		poi[i].y=y;
		cir[i].c.x=x;
		cir[i].c.y=y;
		cir[i].r=1.0;
	}
	Line l;
	int ans=2,cnt,ct;
	Point a[10],b[10];//存两圆间切线经过的两点
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			ct=getTangents(cir[i],cir[j],a,b);
			for(int d=0;d<ct;d++){
				cnt=0;
				l.v=(a[d]-b[d]);
				l.v=l.v/sqrt(l.v.x*l.v.x + l.v.y*l.v.y);
				l.p=b[d];
				for(int k=1;k<=n;k++){
					if(getLineCircleIntersection(l,cir[k])) cnt++;
				}
				ans=max(ans,cnt);
			}
		}
	}
	if(n==1) ans=1;
	cout<<ans;
	return 0;
}

求两个多边形并的面积

hdu3060:Area2

theme:给定两个多边形(凸或凹都可以),求它们交的面积。

//theme:给定两个多边形(凸或凹都可以),求它们交的面积。
#include <bits/stdc++.h>
using namespace std;
 
const int maxn = 550;
const double eps = 1e-8;
int dcmp(double x)
{
    if(x > eps) return 1;
    return x < -eps ? -1 : 0;
}
struct Point
{
    double x, y;
};
double cross(Point a,Point b,Point c) ///叉积
{
    return (a.x-c.x)*(b.y-c.y)-(b.x-c.x)*(a.y-c.y);
}
Point intersection(Point a,Point b,Point c,Point d)
{
    Point p = a;
    double t =((a.x-c.x)*(c.y-d.y)-(a.y-c.y)*(c.x-d.x))/((a.x-b.x)*(c.y-d.y)-(a.y-b.y)*(c.x-d.x));
    p.x +=(b.x-a.x)*t;
    p.y +=(b.y-a.y)*t;
    return p;
}
//计算多边形面积
double PolygonArea(Point p[], int n)
{
    if(n < 3) return 0.0;
    double s = p[0].y * (p[n - 1].x - p[1].x);
    p[n] = p[0];
    for(int i = 1; i < n; ++ i)
        s += p[i].y * (p[i - 1].x - p[i + 1].x);
    return fabs(s * 0.5);
}
double CPIA(Point a[], Point b[], int na, int nb)//ConvexPolygonIntersectArea
{
    Point p[20], tmp[20];
    int tn, sflag, eflag;
    a[na] = a[0], b[nb] = b[0];
    memcpy(p,b,sizeof(Point)*(nb + 1));
    for(int i = 0; i < na && nb > 2; i++)
    {
        sflag = dcmp(cross(a[i + 1], p[0],a[i]));
        for(int j = tn = 0; j < nb; j++, sflag = eflag)
        {
            if(sflag>=0) tmp[tn++] = p[j];
            eflag = dcmp(cross(a[i + 1], p[j + 1],a[i]));
            if((sflag ^ eflag) == -2)
                tmp[tn++] = intersection(a[i], a[i + 1], p[j], p[j + 1]); ///求交点
        }
        memcpy(p, tmp, sizeof(Point) * tn);
        nb = tn, p[nb] = p[0];
    }
    if(nb < 3) return 0.0;
    return PolygonArea(p, nb);
}
double SPIA(Point a[], Point b[], int na, int nb)///SimplePolygonIntersectArea 调用此函数
{
    int i, j;
    Point t1[4], t2[4];
    double res = 0, num1, num2;
    a[na] = t1[0] = a[0], b[nb] = t2[0] = b[0];
    for(i = 2; i < na; i++)
    {
        t1[1] = a[i-1], t1[2] = a[i];
        num1 = dcmp(cross(t1[1], t1[2],t1[0]));
        if(num1 < 0) swap(t1[1], t1[2]);
        for(j = 2; j < nb; j++)
        {
            t2[1] = b[j - 1], t2[2] = b[j];
            num2 = dcmp(cross(t2[1], t2[2],t2[0]));
            if(num2 < 0) swap(t2[1], t2[2]);
            res += CPIA(t1, t2, 3, 3) * num1 * num2;
        }
    }
    return PolygonArea(a, na) + PolygonArea(b, nb) - res;//res为两凸多边形的交的面积 
}
Point p1[maxn], p2[maxn];
int n1, n2;
int main()
{
    while(scanf("%d%d",&n1,&n2)!=EOF)
    {
        for(int i = 0; i < n1; i++) scanf("%lf%lf", &p1[i].x, &p1[i].y);
        for(int i = 0; i < n2; i++) scanf("%lf%lf", &p2[i].x, &p2[i].y);
        double Area = SPIA(p1, p2, n1, n2);
        Area=PolygonArea(p1,n1)+PolygonArea(p2,n2)-Area;
        printf("%.2f\n",Area);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值