计算几何技巧
基础知识
-
点积:
-
几何意义 在上的投影与的长度的乘积。
-
应用:
判断两个向量是否垂直:
求两个向量的夹角:,若为钝角,为锐角
-
-
叉积:
-
几何意义:与张成的平行四边形的有向面积。在的逆时针方向为正。
-
应用:
假如在的左边,则有向面积为正,假如在右边则为负。假如共线,则叉积为。
-
-
直线相关
-
直线的表现形式:
一般形式:
点向式:
斜截式:
-
-
多边形
-
面积:
叉积计算
海伦公式(已知边长计算三角形面积):,则
皮克定理:指一个计算点阵中顶点在格点上的多边形面积公式该公式可以表示为,其中表示多边形内部的点数,表示多边形边界上的点数,表示多边形的面积。
辛普森积分:对于一个二次函数,有。如果不是二次函数,就每次细分,最后细分到一定程度这部分的面积就可以套用这个二次函数的面积公式。
-
三角形的四心:
外心:外接圆圆心,三边中垂线交点,到三角形三个顶点的距离相等
内心:内切圆圆心,角平分线交点,到三边距离相等
垂心:三条垂线交点
重心:三条中线交点(到三角形三顶点距离的平方和最小的点,三角形内到三边距离之积最大的点)
-
-
体积
-
球的体积交,设两球的半径分别为(),球心间距
-
:两球不相交,
-
:小球在大球里面,
-
:
-
-
-
凸包
- 判断点是否在凸包上
-
半平面交
- 一条直线把一个平面划分成两个部分,如果把一个个点凸包的每条边按照逆时针连接,并按照顺序给每条边一个方向,那么凸包可以看做是这条有向直线的左半平面交
-
最小圆覆盖
- 最小覆盖圆是唯一的
- 若 不在 的最小覆盖圆内部,则 必在的最小覆盖圆边上
-
旋转卡壳
-
对踵点对
-
求解问题:
- 计算距离
- 凸多边形直径
- 凸多边形宽
- 凸多边形间最大距离
- 凸多边形间最小距离
- 外接矩形
- 最小面积外接矩形
- 最小周长外界矩形
- 计算距离
-
闵可夫斯基和
- 点集,则,则称为和的闵可夫斯基和
- 先求形状,最后图形的形状是两个多线性的边界向量排序后按照顺序相接,即把两凸包的边向量极角排序后按照逆时针顺序相连
- 两条凸包的闵可夫斯基和也是凸的
JSOI2018
Problem
给定两个凸包,每次给定一个方向向量,问凸包沿着移动后是否会和有交。
Solve
,则有交说明,等价于,把取反后和求闵可夫斯基和判断是否在所形成的的凸包里面即可。
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+10;
struct Point{
ll x,y;
Point operator + (const Point &t)const{
return Point{x+t.x,y+t.y};
}
Point operator - (const Point &t)const{
return Point{x-t.x,y-t.y};
}
ll operator ^ (const Point &t)const{ //叉积
return x*t.y-y*t.x;
}
ll len(){
return x*x+y*y;
}
}A[N],B[N];
int n,m,q;
bool cmp1(Point a,Point b){
return a.x==b.x?a.y<b.y:a.x<b.x;
}
bool cmp2(Point a,Point b){
return (a^b)>0||((a^b)==0&&(a.len()<b.len()));
}
Point stk[N];
int top;
void Convex(Point P[],int &n){
sort(P+1,P+1+n,cmp1);
Point base=P[1];
for(int i=1;i<=n;i++) P[i]=P[i]-base;
sort(P+2,P+1+n,cmp2);
stk[top=1]=P[1];
for(int i=2;i<=n;i++){
while(top>=2&&((stk[top]-stk[top-1])^(P[i]-stk[top-1]))<=0) top--;
stk[++top]=P[i];
}
n=top;
for(int i=1;i<=n;i++)P[i]=stk[i]+base;
}
Point s1[N],s2[N],C[N];
int tot;
void Minkowski(){
for(int i=1;i<n;i++) s1[i]=A[i+1]-A[i];s1[n]=A[1]-A[n];
for(int i=1;i<m;i++) s2[i]=B[i+1]-B[i];s2[m]=B[1]-B[m];
C[tot=1]=A[1]+B[1];
int l=1,r=1;
while(l<=n&&r<=m) ++tot,C[tot]=C[tot-1]+((s1[l]^s2[r])>=0?s1[l++]:s2[r++]);
while(l<=n) ++tot,C[tot]=C[tot-1]+s1[l++];
while(r<=m) ++tot,C[tot]=C[tot-1]+s2[r++];
}
bool point_in_conve(Point x){
if((x^C[1])>0||(x^C[tot])<0) return 0;
int id=lower_bound(C+1,C+1+tot,x,cmp2)-C-1;
return ((x-C[id])^(C[id%tot+1]-C[id]))<=0;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin>>n>>m>>q;
for(int i=1;i<=n;i++) cin>>A[i].x>>A[i].y;
for(int i=1;i<=m;i++) cin>>B[i].x>>B[i].y,B[i].x=-B[i].x,B[i].y=-B[i].y;
Convex(A,n);
Convex(B,m);
Minkowski();
Convex(C,tot);
Point base=C[1];
for(int i=1;i<=tot;i++) C[i]=C[i]-base;
while(q--){
Point p;
cin>>p.x>>p.y;
cout<<point_in_conve(p-base)<<'\n';
}
}
反演
- 内容:反演中心为,反演半径为,若经过的直线经过,且,则称关于互为反演
- 性质:
- 一根过的直线的反演是本身
- 一根不过的直线的反演是一个过的圆
- 一个过的圆的反演是一根不过的直线
- 一个不过的圆的反演是一个和该圆关于位似的圆
- 反演不改变相切关系
- 反演代码
- 不过反演中心的圆的反演
Circle Inv_C2C(Point O,double R,Circle A) //反演中心,反演半径,反演圆 { double OA=(A.c-O).len(); double RB=0.5*((1/(OA-A.r)-(1/(OA+A.r))))*R*R; double OB=OA*RB/A.r; double Bx=O.x+(A.c.x-O.x)*OB/OA; double By=O.y+(A.c.y-O.y)*OB/OA; return Circle({Bx,By},RB); }
- 不过反演中心的直线反演成圆
Circle Inv_L2C(Point O,double R,Point A,Point v){ //直线反演成圆,点向式表示直线 Point P=GetLineProjection(O,A,A+v); double d=(O-P).len(); double RB=R*R/(2*d); Point VB=(P-O)/d*RB; return Circle(O+VB,RB); }
- 不过反演中心的圆的反演
HDU 4473 [Problem of Apollonius]
Problem
求过两圆外一点,且与两圆相切的所有的圆。
Solve
以所给点为反演中心,原来的两个圆反演后还是圆,所求的圆反演后是直线,问题就变成了求与反演后的圆相切的全部直线,这样的直线最多条,求出直线后再对直线反演成圆即可。
两点确定一条直线,切线用两个点来表示,最后切线用点向式表示
切点用极坐标的方式求
外切计算切线的两个点
内切计算切线的两个点
判断两个点是否在直线的同一侧用叉积判断
Code
#include <iostream>
#include <cmath>
#include <algorithm>
#include <iomanip>
using namespace std;
const double eps=1e-8;
const double PI=acos(-1);
int dcmp(double x,double y){
if(fabs(x-y)<eps) return 0;
else if(x<y) return -1;
else return 1;
}
struct Point{
double x,y;
Point operator + (const Point &t) const{
return {x+t.x,y+t.y};
}
Point operator - (const Point &t) const{
return {x-t.x,y-t.y};
}
double operator * (const Point &t) const{
return x*t.x+y*t.y;
}
Point operator * (const double &t) const{
return {x*t,y*t};
}
Point operator / (const double &t) const{
return {x/t,y/t};
}
double operator ^ (const Point &t) const{
return x*t.y-y*t.x;
}
double len(){
return sqrt(x*x+y*y);
}
};
struct Circle{
Point c;
double r;
Circle():c({0,0}),r(0){};
Circle(Point c,double r):c(c),r(r){}
Point point(double a){//用极坐标求圆上点的坐标
return {c.x+cos(a)*r,c.y+sin(a)*r};
}
};
//反演
Circle Inv_C2C(Point O,double R,Circle A) //反演中心,反演半径,反演圆
{
double OA=(A.c-O).len();
double RB=0.5*((1/(OA-A.r)-(1/(OA+A.r))))*R*R;
double OB=OA*RB/A.r;
double Bx=O.x+(A.c.x-O.x)*OB/OA;
double By=O.y+(A.c.y-O.y)*OB/OA;
return Circle({Bx,By},RB);
}
//点在直线上的投影
Point GetLineProjection(Point P, Point A, Point B) {
Point v = B - A;
return A + v*((v*(P - A)) / (v*v));
}
Circle Inv_L2C(Point O,double R,Point A,Point v){ //直线反演成圆,点向式表示直线
Point P=GetLineProjection(O,A,A+v);
double d=(O-P).len();
double RB=R*R/(2*d);
Point VB=(P-O)/d*RB;
return Circle(O+VB,RB);
}
//用两点确定一个直线
int getTangents(Circle A,Circle B,Point a[],Point b[]){
int cnt=0;
if(A.r<B.r){
swap(A,B);
swap(a,b);
}
double d=(A.c-B.c).len();
double Lr=A.r-B.r;
double Rr=A.r+B.r;
//内含
// cout<<d<<" "<<Lr<<'\n';
if(dcmp(d,Lr)==-1) return 0;
double base=atan2(B.c.y-A.c.y,B.c.x-A.c.x);
//两个圆相等,无数条
if(dcmp(d,0)==0&&dcmp(Lr,0)==0) return -1;
//内切
if(dcmp(d,Lr)==0){ //A,B圆心连线上与A,B的两个交点
a[cnt]=A.point(base);
b[cnt]=B.point(base);
++cnt;
return 1;
}
//外切
//两条外公切线
double ang=acos(Lr/d);
a[cnt]=A.point(base+ang);
b[cnt]=B.point(base+ang);
++cnt;
a[cnt]=A.point(base-ang);
b[cnt]=B.point(base-ang);
++cnt;
//一条内公切线
if(dcmp(d,Rr)==0){
a[cnt]=A.point(base);
b[cnt]=B.point(PI+base);
++cnt;
}else if(dcmp(d,Rr)==1){
double ang=acos(Rr/d);
a[cnt]=A.point(base+ang);
b[cnt]=B.point(PI+base+ang);
cnt++;
a[cnt]=A.point(base-ang);
b[cnt]=B.point(PI+base-ang);
cnt++;
}
return cnt;
}
//判断A,B两点是否在直线同侧
bool theSameSideOfLine(Point A,Point B,Point S,Point v){
return dcmp((A-S)^v,0)*dcmp((B-S)^v,0)>0;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--){
Circle A,B;
Point P;
cin>>A.c.x>>A.c.y>>A.r;
cin>>B.c.x>>B.c.y>>B.r;
cin>>P.x>>P.y;
Circle invA=Inv_C2C(P,10,A);
Circle invB=Inv_C2C(P,10,B);
// cout<<invA.c.x<<" "<<invA.c.x<<" "<<invA.r<<'\n';
// cout<<invB.c.x<<" "<<invB.c.x<<" "<<invB.r<<'\n';
Point LA[4],LB[4];
Circle ansC[4];
int q=getTangents(invA,invB,LA,LB);
int ans=0;
for(int i=0;i<q;i++){
if(theSameSideOfLine(invA.c,invB.c,LA[i],LB[i]-LA[i])){
if(!theSameSideOfLine(P,invA.c,LA[i],LB[i]-LA[i])) continue;
ansC[ans++]=Inv_L2C(P,10,LA[i],LB[i]-LA[i]);
}
}
cout<<ans<<'\n';
for(int i=0;i<ans;i++)
cout<<fixed<<setprecision(10)<<ansC[i].c.x<<" "<<ansC[i].c.y<<" "<<ansC[i].r<<'\n';
}
}
HDU 6158
关键点思想 1
- 计算几何中涉及到的变量往往是连续的
- 行走的距离、转弯的角度等
- 因此,最优化问题中,方案集合通常是无穷的
- 找到有限集满足:
- 包含最优解
- 所有元素都是合法解或者所有非法解都能被排除
Tokyo2014 H
多校 2018 9E
关键点思想 2
- 计算几何中经常会遇到分段函数
- 沿着折线行走时,对一个点的距离
- 虽然整体复杂,但是每一段都是简单函数
- 线性函数、二次函数、凸函数
- 找到分界点的一个超集,分段考虑
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现