【学习笔记】【数学】计算几何基础
点击查看目录
前置知识:
建议虽然是简单的前置知识,还是打开略过一遍。
-
浮点数与误差分析(少用除法)
-
向量相关
向量
向量,就是带有方向和大小两个属性的边,通常形式为。
运算与性质:
- 判等:两点坐标重合。
il int dcmp(double a){ if(a<-eps) return -1; if(a>eps) return 1; return 0; } il bool operator==(node a,node b)<% return !dcmp(a.x-b.x) && !dcmp(a.y-b.y); %>
- 加减:。
il node operator+(node a,node b)<% return node(a.x+b.x,a.y+b.y); %> il node operator-(node a,node b)<% return node(a.x-b.x,a.y-b.y); %>
- 模长:。
il double len(node a)<% return sqrt(dot(a,a)); %> //dot是点乘函数
-
角度:
-
数乘:。
il node operator*(node a,double k)<% return node(a.x*k,a.y*k); %>
- 点乘:。
(几何意义: 乘上 在 上的投影,即)
il double dot(node a,node b)<% return a.x*b.x+a.y*b.y; %>
- 夹角:。
il double angle(node a,node b)<% return acos(dot(a,b)/len(a)/len(b)); %>
-
若 ,( 不为0)则 与 垂直, 为锐角, 为钝角。
-
叉积:。
(几何意义:叉积的绝对值=面积)
il double cro(node a,node b)<% return a.x*b.y-a.y*b.x; %>
-
面积:。
-
旋转:将 旋转 弧度得到 :
- 直线与线段相关
直线与线段
直线一般形式有四:
其中, 是直线的斜率,, 是直线的截距。
特别地,当两条直线垂直时有 。
而线段,在代码中往往可以这样表示:
struct Node{double x,y;}; struct line{node p1,p2;};
有以下运算:
- 判断点 是否在直线 上。
il int judge_LINE(Node p,Node a,Node b)<% return !mystd::dcmp(Vector::cro(p-a,b-a)); %>
- 判断点 是否在线段 上。
il int judge_line(Node p,Node a,Node b)<% return !mystd::dcmp(cro(p-a,b-a)) && mystd::dcmp(mystd::fMin(a.x,b.x)-p.x)<=0 && mystd::dcmp(p.y-mystd::fMax(a.y,b.y))<=0 && mystd::dcmp(p.x-mystd::fMax(a.x,b.x))<=0 && mystd::dcmp(mystd::fMin(a.y,b.y)-p.y)<=0; %>
- 求点 到直线 的垂足。
il Node footnode(Node p,Node a,Node b){ Node x=p-a,y=p-b,z=b-a; double len1=Vector::dot(x,z)/Vector::len(z),len2=-1.0*Vector::dot(y,z)/Vector::len(z); return a+z*(len1/(len1+len2)); }
证明:见图。
- 求点 到直线 的对称点:
il Node symmetry(Node p,Node a,Node b)<% return p+(footnode(p,a,b)-p)*2; %>
- 求直线 与直线 的交点:
il Node internode(Node a,Node b,Node c,Node d)<% Node x=b-a,y=d-c,z=a-c;return a+x*(Vector::cro(y,z)/Vector::cro(x,y)); %>
证明:相似,见图。
- 判断直线 与线段 是否相交:
il int judge_CROSS(Node a,Node b,Node c,Node d)<% return judge_line(internode(a,b,c,d),c,d); %>
看交点是否在线段 即可。
- 判断线段 与线段 是否相交:
il int judge_cross(Node a,Node b,Node c,Node d){ double c1=cro(b-a,c-a),c2=cro(b-a,d-a); double d1=cro(d-c,a-c),d2=cro(d-c,b-c); return mystd::dcmp(c1)*mystd::dcmp(c2)<0 && mystd::dcmp(d1)*mystd::dcmp(d2)<0; }
看端点是否在两侧即可,根据下面的跨立实验得。
- 判断一点是否在一任意多边形内:
il int PIP(Node *P,int n,Node a){ int cnt=0,j; double res=0.0; for(rg i=1;i<=n;++i){ if(i<n) j=i+1; else j=1; if(judge_line(a,P[i],P[j])) return 2;//点在多边形上 if(a.y>=mystd::fMin(P[i].y,P[j].y) && a.y<mystd::fMax(P[i].y,P[j].y)){ //纵坐标在该线段两端点之间 res=P[i].x+(a.y-P[i].y)/(P[j].y-P[i].y)*(P[j].x-P[i].x); //res是同一纵坐标下的在该边上的点的横坐标 cnt=cnt+(mystd::dcmp(res-a.x)>0); } } return cnt&1; //奇数次就是在多边形内 }
证明见注释。
因此有缺省源:
缺省源
#include<bits/stdc++.h> using namespace std; #define rg register int #define il inline typedef long long ll; typedef long double llf; const double eps=1e-8; namespace mystd{ il int Max(int a,int b)<%if(a<b) return b;return a; %> il int Min(int a,int b)<%if(a>b) return b;return a; %> il int Abs(int a)<% if(a<0) return a*(-1);return a; %> il double fMax(double a,double b)<%if(a<b) return b;return a; %> il double fMin(double a,double b)<%if(a>b) return b;return a; %> il double fAbs(double a)<% if(a<0) return a*(-1);return a; %> il int dcmp(double a){ if(a<-eps) return -1; if(a>eps) return 1; return 0; } } struct Node<% double x,y; Node(double xx=0,double yy=0)<% x=xx,y=yy; %> %>; il bool operator==(Node a,Node b)<% return !mystd::dcmp(a.x-b.x) && !mystd::dcmp(a.y-b.y); %> il Node operator+(Node a,Node b)<% return Node(a.x+b.x,a.y+b.y); %> il Node operator-(Node a,Node b)<% return Node(a.x-b.x,a.y-b.y); %> il Node operator*(Node a,double k)<% return Node(a.x*k,a.y*k); %> namespace Vector{ il double dot(Node a,Node b)<% return a.x*b.x+a.y*b.y; %> //点乘 il double len(Node a)<% return sqrt(dot(a,a)); %> //模长 il double angle(Node a,Node b)<% return acos(dot(a,b)/len(a)/len(b)); %>//夹角 il double cro(Node a,Node b)<% return a.x*b.y-a.y*b.x; %> //叉积 il int judge_LINE(Node p,Node a,Node b)<% return !mystd::dcmp(cro(p-a,b-a)); %> //判断p是否在直线AB上 il int judge_line(Node p,Node a,Node b)<% return !mystd::dcmp(cro(p-a,b-a)) && mystd::dcmp(mystd::fMin(a.x,b.x)-p.x)<=0 && mystd::dcmp(p.y-mystd::fMax(a.y,b.y))<=0 && mystd::dcmp(p.x-mystd::fMax(a.x,b.x))<=0 && mystd::dcmp(mystd::fMin(a.y,b.y)-p.y)<=0; %> //判断p是否在线段AB上 il Node footnode(Node p,Node a,Node b){ Node x=p-a,y=p-b,z=b-a; double len1=dot(x,z)/len(z),len2=-1.0*dot(y,z)/len(z); return a+z*(len1/(len1+len2)); } //求p到直线AB的垂足 il Node symmetry(Node p,Node a,Node b)<% return p+(footnode(p,a,b)-p)*2; %> //求p到直线AB的对称点 il Node internode(Node a,Node b,Node c,Node d)<% Node x=b-a,y=d-c,z=a-c;return a+x*(cro(y,z)/cro(x,y)); %> //直线AB与CD交点 il int judge_CROSS(Node a,Node b,Node c,Node d)<% return judge_line(internode(a,b,c,d),c,d); %> //判断直线AB与线段CD是否有交点 il int judge_cross(Node a,Node b,Node c,Node d){ double c1=cro(b-a,c-a),c2=cro(b-a,d-a); double d1=cro(d-c,a-c),d2=cro(d-c,b-c); return mystd::dcmp(c1)*mystd::dcmp(c2)<0 && mystd::dcmp(d1)*mystd::dcmp(d2)<0; } //判断线段AB与线段CD是否有交点 il int PIP(Node *P,int n,Node a){ int cnt=0,j; double res=0.0; for(rg i=1;i<=n;++i){ if(i<n) j=i+1; else j=1; if(judge_line(a,P[i],P[j])) return 2;//点在多边形上 if(a.y>=mystd::fMin(P[i].y,P[j].y) && a.y<mystd::fMax(P[i].y,P[j].y)){ //纵坐标在该线段两端点之间 res=P[i].x+(a.y-P[i].y)/(P[j].y-P[i].y)*(P[j].x-P[i].x); //res是同一纵坐标下的在该边上的点的横坐标 cnt=cnt+(mystd::dcmp(res-a.x)>0); } } return cnt&1; //奇数次就是在多边形内 } //判断点a是否在多边形P内部 }
叉积与跨立实验
我们在前置知识中已经提到叉积,但是叉积的什么“右手定则”我不会,长大再学(
上面提到,对于向量 ,有叉积:。
(下有证明,别急)
以 为参考点,有代码:
叉积
double multi(node p1, node p2, node p0) { double x1, y1, x2, y2; x1 = p1.x - p0.x; y1 = p1.y - p0.y; x2 = p2.x - p0.x; y2 = p2.y - p0.y; return x1 * y2 - x2 * y1; }
而该函数返回值若大于 ,证明 在 逆时针方向,小于 则在顺时针方向,若等于 则共线。
为什么?我们现在来证明这个公式。
所以,当 ,意味着 ,就是 在 的逆时针方向。
例题:
[SCOI2007] 折纸
[SCOI2007] 折纸
题目描述
桌上有一张边界平行于坐标轴的正方形纸片,左下角的坐标为(0,0),右上角的坐标为(100,100)。接下来执行n条折纸命令。每条命令用两个不同点P1(x1,y1)和P2(x2,y2)来表示,执行时把当前的折纸作品沿着P1P2所在直线折叠,并把有向线段P1P2的右边折向左边(左边的部分保持不变)。
折叠结束后,需要在作品上打一个孔,然后用绳子穿起来挂在墙上。孔的位置是相当重要的:若需要穿过太多层的纸,打孔本身比较困难;若穿过的层数太少,悬挂起来以后作品可能会被撕破。为了选择一个比较合适的打孔位置,你需要计算在每个候选位置打孔时穿过的层数。如果恰好穿过某一层的边界(误差0.000001内),则该层不统计在结果中。
本题考虑一个简化的模型:纸的厚度不计,因此折纸操作总能完美执行。
输入格式
输入第一行为一个整数n,即折纸的次数。以下n行每行四个实数x1,y1,x2,y2,表示每次折纸时对应的有向线段。
下一行包含一个正整数m,即候选位置的个数,以下每行包含两个实数x,y,表示一个候选位置。
输出格式
每个候选位置输出一行,包含一个整数,即该位置打孔时穿过的层数。
样例 #1
样例输入 #1
2 -0.5 -0.5 1 1 1 75 0 75 6 10 60 80 60 30 40 10 10 50 50 20 50
样例输出 #1
4 2 2 0 0 2
提示
20%的数据满足:n<=1
100%的数据满足:0<=n<=8, 1<=m<=50
解题:
这个题数据范围 非常的小,可以暴力。
折叠嘛,给出来一条线,分类讨论:
-
把线右边的折到左边,用到上面的跨立实验和求对称点。
-
线上的不用动。
-
如果不在右边也不在线上就更不用动了。
我们开两个点结构体,分别是 和 ,表示原本在线右边的点折叠后位置,原本在线左边的点折叠后位置。
第一种情况是将对称点进入 。
第二种情况是将线上点进入 与 。
第三种情况是将左边原点进入 。
我们最开始的顶点是很容易得到的,一个正方形嘛。
因此先将顶点进行这样的操作,之后,我们要考虑这个边与线的交点,入 。
但是呢,因为可能是三边交点相同,所以要去重。
得到一堆新的多边形,作为新的图形重复操作。
最后对于一个点暴力枚举所有多边形是否包含它。
Miku's Code
#include<bits/stdc++.h> using namespace std; #define rg register int #define il inline typedef long long ll; typedef long double llf; const double eps=1e-8; namespace mystd{ il int Max(int a,int b)<%if(a<b) return b;return a; %> il int Min(int a,int b)<%if(a>b) return b;return a; %> il int Abs(int a)<% if(a<0) return a*(-1);return a; %> il double fMax(double a,double b)<%if(a<b) return b;return a; %> il double fMin(double a,double b)<%if(a>b) return b;return a; %> il double fAbs(double a)<% if(a<0) return a*(-1);return a; %> il int dcmp(double a){ if(a<-eps) return -1; if(a>eps) return 1; return 0; } } struct Node<% double x,y; Node(double xx=0,double yy=0)<% x=xx,y=yy; %> %>; il bool operator==(Node a,Node b)<% return !mystd::dcmp(a.x-b.x) && !mystd::dcmp(a.y-b.y); %> il Node operator+(Node a,Node b)<% return Node(a.x+b.x,a.y+b.y); %> il Node operator-(Node a,Node b)<% return Node(a.x-b.x,a.y-b.y); %> il Node operator*(Node a,double k)<% return Node(a.x*k,a.y*k); %> namespace Vector{ il double dot(Node a,Node b)<% return a.x*b.x+a.y*b.y; %> //点乘 il double len(Node a)<% return sqrt(dot(a,a)); %> //模长 il double angle(Node a,Node b)<% return acos(dot(a,b)/len(a)/len(b)); %>//夹角 il double cro(Node a,Node b)<% return a.x*b.y-a.y*b.x; %> //叉积 il int judge_LINE(Node p,Node a,Node b)<% return !mystd::dcmp(cro(p-a,b-a)); %> //判断p是否在直线AB上 il int judge_line(Node p,Node a,Node b)<% return !mystd::dcmp(cro(p-a,b-a)) && mystd::dcmp(mystd::fMin(a.x,b.x)-p.x)<=0 && mystd::dcmp(p.y-mystd::fMax(a.y,b.y))<=0 && mystd::dcmp(p.x-mystd::fMax(a.x,b.x))<=0 && mystd::dcmp(mystd::fMin(a.y,b.y)-p.y)<=0; %> //判断p是否在线段AB上 il Node footnode(Node p,Node a,Node b){ Node x=p-a,y=p-b,z=b-a; double len1=dot(x,z)/len(z),len2=-1.0*dot(y,z)/len(z); return a+z*(len1/(len1+len2)); } //求p到直线AB的垂足 il Node symmetry(Node p,Node a,Node b)<% return p+(footnode(p,a,b)-p)*2; %> //求p到直线AB的对称点 il Node internode(Node a,Node b,Node c,Node d)<% Node x=b-a,y=d-c,z=a-c;return a+x*(cro(y,z)/cro(x,y)); %> //直线AB与CD交点 il int judge_CROSS(Node a,Node b,Node c,Node d)<% return judge_line(internode(a,b,c,d),c,d); %> //判断直线AB与线段CD是否有交点 il int judge_cross(Node a,Node b,Node c,Node d){ double c1=cro(b-a,c-a),c2=cro(b-a,d-a); double d1=cro(d-c,a-c),d2=cro(d-c,b-c); return mystd::dcmp(c1)*mystd::dcmp(c2)<0 && mystd::dcmp(d1)*mystd::dcmp(d2)<0; } //判断线段AB与线段CD是否有交点 il int PIP(Node *P,int n,Node a){ int cnt=0,j; double res=0.0; for(rg i=1;i<=n;++i){ if(i<n) j=i+1; else j=1; if(judge_line(a,P[i],P[j])) return 2;//点在多边形上 if(a.y>=mystd::fMin(P[i].y,P[j].y) && a.y<mystd::fMax(P[i].y,P[j].y)){ //纵坐标在该线段两端点之间 res=P[i].x+(a.y-P[i].y)/(P[j].y-P[i].y)*(P[j].x-P[i].x); //res是同一纵坐标下的在该边上的点的横坐标 cnt=cnt+(mystd::dcmp(res-a.x)>0); } } return cnt&1; //奇数次就是在多边形内 } //判断点a是否在多边形P内部 } const int maxn=8; Node a,b; struct ewq{ int ncnt; Node node[maxn*maxn+5]; };ewq py[(1<<maxn)+5],qy[(1<<maxn)+5]; int T,n,t,tt; il bool judge(Node a,Node l,Node r){ //判断AL是否在AR的顺时针方向即右边 return mystd::dcmp(Vector::cro(l-a,r-a))>0; } il void cut(ewq e,Node a,Node b){ ewq L,R; L.ncnt=R.ncnt=0; for(rg i=1;i<=e.ncnt;++i){ if(judge(a,e.node[i],b)) R.node[++R.ncnt]=Vector::symmetry(e.node[i],a,b); //在右边就折叠 else if(Vector::judge_LINE(e.node[i],a,b)) L.node[++L.ncnt]=R.node[++R.ncnt]=e.node[i]; else L.node[++L.ncnt]=e.node[i]; rg j; if(i<e.ncnt) j=i+1; else j=1; if(Vector::judge_CROSS(a,b,e.node[i],e.node[j])){ //如果直线AB与线段node[i]-node[i+1]有交点,交点进入 L.node[++L.ncnt]=R.node[++R.ncnt]=Vector::internode(a,b,e.node[i],e.node[j]); } while(L.ncnt>1 && L.node[L.ncnt]==L.node[L.ncnt-1]) --L.ncnt; while(R.ncnt>1 && R.node[R.ncnt]==R.node[R.ncnt-1]) --R.ncnt; //去重 } if(L.ncnt>1 && L.node[1]==L.node[L.ncnt]) --L.ncnt; if(R.ncnt>1 && R.node[1]==R.node[R.ncnt]) --R.ncnt; if(L.ncnt) qy[++tt]=L; if(R.ncnt) qy[++tt]=R; } il void pre(){ //初始化100*100正方形 py[++t].ncnt=4; py[t].node[1]=Node<% 0,0 %>; py[t].node[2]=Node<% 0,100 %>; py[t].node[3]=Node<% 100,100 %>; py[t].node[4]=Node<% 100,0 %>; } int main(){ #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif scanf("%d",&n); pre(); double x,y; while(n--){ tt=0; scanf("%lf %lf",&x,&y);a.x=x,a.y=y; scanf("%lf %lf",&x,&y);b.x=x,b.y=y; for(rg i=1;i<=t;++i) cut(py[i],a,b); t=tt; for(rg i=1;i<=tt;++i) py[i]=qy[i]; } // for(int i=1;i<=t;++i){ // printf("$$%.2lf %.2lf\n",py[i].node[i].x,py[i].node[i].y); // } scanf("%d",&T); while(T--){ scanf("%lf %lf",&x,&y); a.x=x,a.y=y; int ans=0; for(rg i=1;i<=t;++i){ if(Vector::PIP(py[i].node,py[i].ncnt,a)==1) ++ans; } printf("%d\n",ans); } return 0; } /* 2 -0.5 -0.5 1 1 1 75 0 75 6 10 60 80 60 30 40 10 10 50 50 20 50 */
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!