计算几何 val.2

目录

  • 计算几何 val.2

    • 几何单位结构体板子
    • 旋转卡壳
      • 基础概念
      • 求法
      • 模板
    • 半平面交
      • 前置芝士:线段交
      • S&I算法
      • 模板
    • 最小圆覆盖
      • 随机增量法
      • 时间复杂度
      • 模板
    • 后记

计算几何 val.2

前置芝士:基础操作以及凸包

本文主要写旋转卡壳、半平面交、最小圆覆盖要注意的内容

几何单位结构体板子

不全(我知道

struct point{
    double x,y;
    point(double x=0,double y=0): x(x),y(y){} //构造函数,非常方便
    double operator*(point b){ //叉积
        return x*b.y-y*b.x;
    }
    double operator^(point b){ //点积
        return x*b.x+y*b.y;
    }
    point operator-(point b){
        return point(x-b.x,y-b.y);
    }
    point operator+(point b){
        return point(x+b.x,y+b.y);
    }//向量运算
    point operator*(double b){ //数乘
        return point(x*b,y*b);
    }
    db dis(){ //模长
        return sqrt(x*x+y*y);
    }
}b[N];
int comp0(double x){
    return fabs(x)<=eps?0:(x>0?1:-1);
}//判0,防止精度误差
struct line{
    point p,v;//p起点,v终点 ,表示线段
    double theta;
    bool operator <(line y){
        return comp0(theta-y.theta)==0?comp0((y.v-p)*(v-p))<0:comp0(theta-y.theta)<0;
    }//排序,保证第一关键字是极角,第二关键字是与右边的距离(用叉积判相对关系,在左边的放后面)
};
point inter(line a,line b){//交点  intersection
    point v1=a.v-a.p,v2=b.v-b.p;
    return b.p+v2*(((b.p-a.p)*v1)/(v1*v2));
}//此处默认了没有平行的情况 , 判断线段有没有交点就是判断点是否在线段上,但是半平面交是直线(只是用线段表示)

旋转卡壳

\[
\Large{\text{旋转卡壳(ko)}}
\]
对,非常正确

此算法用来求凸多边形直径

基础概念

  1. 切线

    过顶点的一条线,这个多边形都在这条线的一侧

  2. 对踵点

    多边形上两个点作一对平行线,整个多边形都在这两条线之间

求法

考虑逆时针枚举每条边并找到距离这条边最远的点,那么这个点和这条边的一个顶点构成的直径是可能的答案

我们惊奇地发现,点出现的顺序也是逆时针的,那么就可以\(O(n)\)求出了

(当然求凸包还要\(n\log n\)

补充:如果要求单独一条边的最远点的话,可以三分求(单峰函数)

模板

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define db double
using namespace std;
struct point{
    double x,y;
    point(double x=0,double y=0): x(x),y(y){}
    double operator*(point b){
        return x*b.y-y*b.x;
    }
    point operator-(point b){
        return point(x-b.x,y-b.y);
    }
    point operator+(point b){
        return point(x+b.x,y+b.y);
    }
    db dis(){
        return sqrt(x*x+y*y);
    }
};
const int N = 50021;
point p[N],h[N];
int tp=0,stk[N],usd[N];
int cmp(point a,point b){
    return a.x==b.x?a.y<b.y:a.x<b.x;
}
int n=0;
db Fabs(db a){
    return a>0?a:-a;
}
db disl(point a,point b,point x){
    return Fabs((a-x)*(b-x)/(a-b).dis());
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%lf%lf",&p[i].x,&p[i].y);
    }
    sort(p+1,p+n+1,cmp);
    stk[++tp]=1;
    for(int i=2;i<=n;i++){
        while(tp>1&&(p[stk[tp]]-p[stk[tp-1]])*(p[i]-p[stk[tp]])<=0) usd[stk[tp--]]=0;
        usd[i]=1;stk[++tp]=i;
    }
    int ntp=tp;
    for(int i=n-1;i>=1;i--){
        if(!usd[i]){
            while(tp>ntp&&(p[stk[tp]]-p[stk[tp-1]])*(p[i]-p[stk[tp]])<=0) usd[stk[tp--]]=0;
            usd[i]=1;stk[++tp]=i;
        }
    }
    for(int i=1;i<=tp;i++){
        h[i]=p[stk[i]];
    }
    if(tp<=2){ //只有一个点
        puts("0");
        return 0;
    }
    if(tp==3){
        printf("%.0lf\n",(h[1]-h[2]).dis()*(h[1]-h[2]).dis());
        return 0;
    }
    db ans=0;
    int t=1;
    //注意最后一个点(就是1号点)要保留,因为后面有h[i+1],[t+1],方便操作
    for(int i=1;i<tp;i++){
        while(disl(h[i],h[i+1],h[t])<disl(h[i],h[i+1],h[t+1])) t=t%(tp-1)+1; //逆时针枚举点
        ans=max(ans,max((h[i]-h[t]).dis(),(h[i+1]-h[t]).dis())); //两端点到此点
    }
    printf("%.0f",ans*ans);
    return 0;
}

半平面交

半平面此处我们用向量表示,以左侧或右侧为半平面

前置芝士:线段交

画图吧,用相似得出
\[
h_1=\frac{fabs((v_2-p_1)*(p_2-p_1))}{dis(v_2-p_2)},h_2=\frac{fabs((v_2-v_1)*(p_2-v_1))}{dis(v_2-p_2)}
\]

\[
line(p_1,v_1) \cap line(p_2,v_2)=p_1+(v_1-p_1)*\frac{h_1}{h_1+h_2}
\]

S&I算法

极角排序

令\(\theta=arctan\frac y x\),以其从小到大排序,得到新的向量集

算法流程

  1. 以逆时针为正方向,建边(线段)
  2. 对线段根据极角排序
  3. 去除极角相同的情况下,位置在右边的边(如果求的是左半平面交(逆时针凸多边形)的话)
  4. 双端队列储存线段集合 \(L\),遍历所有线段
  5. 判断该线段加入后对半平面交的影响,(对双端队列的头部和尾部进行判断,因为线段加入是有序的,有影响是指交点在这条线的另一边(不需要的一边)),即判断之前的交点是否在次线段右侧
  6. 上一步一定要先删尾部再删头部,具体见wjyyy大佬的博客,后文会附上
  7. 最后判断形成环的影响(尾部再更新头部一次,头部再更新尾部一次)
  8. 最后剩下的线段集合 \(L\),即使最后要求的半平面交
  9. 全程都可以用叉积判方向

模板

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define db double
const double eps=1e-9;
using namespace std;
const int N = 10001;
struct point{
    double x,y;
    point(double x=0,double y=0): x(x),y(y){}
    double operator*(point b){ //叉积
        return x*b.y-y*b.x;
    }
    double operator^(point b){ //点积
        return x*b.x+y*b.y;
    }
    point operator-(point b){
        return point(x-b.x,y-b.y);
    }
    point operator+(point b){
        return point(x+b.x,y+b.y);
    }
    point operator*(double b){ //数乘
        return point(x*b,y*b);
    }
    db dis(){ //模长
        return sqrt(x*x+y*y);
    }
}b[N];
int comp0(double x){
    return fabs(x)<=eps?0:(x>0?1:-1);
}
struct line{
    point p,v;//p起点,v终点 ,表示线段
    double theta;
    bool operator <(line y){
        return comp0(theta-y.theta)==0?comp0((y.v-p)*(v-p))<0:comp0(theta-y.theta)<0;
    }//排序,保证第一关键字是极角,第二关键字是与右边的距离(用叉积判相对关系,在左边的放后面)
};
point inter(line a,line b){//交点  intersection
    point v1=a.v-a.p,v2=b.v-b.p;
    return b.p+v2*(((b.p-a.p)*v1)/(v1*v2));
}//此处默认了没有平行的情况 , 判断线段有没有交点就是判断点是否在线段上,但是半平面交是直线(只是用线段表示)
int out(line a,line b,line k){
    point ins=inter(a,b); //此处要判断的是ins是否在k的右边,画个图
    return comp0((k.v-k.p)*(ins-k.p))<0;
}
int n;
line a[N],q[N];int top;
void work(){
    sort(a+1,a+top+1);
    int cnt=0;
    for(int i=1;i<=top;i++){
        if(comp0(a[i].theta-a[i-1].theta)!=0) cnt++;
        a[cnt]=a[i];//极角相同时,右边的更优(排序保证了在左边)
    }
    int h=1,t=0;
    q[++t]=a[1],q[++t]=a[2];//既然是交,至少包含两个元素
    for(int i=3;i<=cnt;i++){
        while(h<t&&out(q[t-1],q[t],a[i])) t--; //踢掉一些点 ,注意一定要先踢后面,不然会有些错
        while(h<t&&out(q[h+1],q[h],a[i])) h++;
        q[++t]=a[i];
    }
    while(h<t&&out(q[t-1],q[t],q[h])) t--; //由于是环形,判断影响
    while(h<t&&out(q[h+1],q[h],q[t])) h++;
    q[t+1]=q[h];
    top=0;
    for(int i=h;i<=t;i++){
        b[++top]=inter(q[i],q[i+1]);
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        int k;scanf("%d",&k);
        for(int j=1;j<=k;j++){
            scanf("%lf%lf",&b[j].x,&b[j].y);
        }
        b[k+1]=b[1];
        for(int j=1;j<=k;j++){
            a[++top].p=b[j];a[top].v=b[j+1];//逆时针给出,如果不知道的话可以叉积判断
        }
    }
    for(int i=1;i<=top;i++){
        a[i].theta=atan2(a[i].v.y-a[i].p.y,a[i].v.x-a[i].p.x);
    }
    work();
    double ans=0;
    if(top<=2){
        printf("%.3lf",0.0);return 0;//二 边 形
    }
    b[top+1]=b[1]; //同样,环形处理方法
    for(int i=1;i<=top;i++){
        ans+=(b[i]*b[i+1]);
    }
    ans/=2.0;
    if(comp0(ans)==0) printf("%.3lf",0.0);
    else{
        printf("%.3lf",fabs(ans));
    }
    return 0;
}

最小圆覆盖

使用随机增量法

随机增量法

考虑钦定\(i,j\)一定在圆上,\(j < i\)

对于\(\forall k<j\),考虑构造覆盖\(i,j,k\)的圆

如果当前\(k\)在圆内,计算\(k+1\)

否则更新为\(i,j,k\) 的外接圆(三点确定圆)

对于前面两层,如果某个点在圆内,直接跳过

否则枚举\(j\in [1,i)\) ,重新计算

时间复杂度

看起来是三层循环啊QwQ,在圆内的点再多也优化不了多少吧?

但是我们最开始 \(\text{random_suffle}\) 了一下,于是复杂度变成了期望意义下的

那么是多少呢?男默女泪的是,它达到了惊人的\(O(n)\)!

分析:

  1. 对于过\(P_i,P_j\)的圆,每个点至少循环了一遍,为\(O(j)\)
  2. 对于过\(P_i\)的圆,考虑覆盖前\(i\)个点的圆是由3个点确定的,于是在前\(i-1\)个点中,期望有2个点计算了下一层
  3. 对于所有点的最小圆覆盖,期望有三个点能做出贡献
  4. 总复杂度:\(O\left(\sum_{i=1}^{n} \frac{3}{i} \sum_{j=1}^{i} \frac{2}{j} \cdot j\right)=O(6\cdot n)=O(n)\)

模板

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define db double
#include<cstdlib>
#include<ctime>
const double eps=1e-17;
using namespace std;
const int N = 100001;
struct point{
    double x,y;
    point(double x=0,double y=0): x(x),y(y){}
    double operator*(point b){ //叉积
        return x*b.y-y*b.x;
    }
    double operator^(point b){ //点积
        return x*b.x+y*b.y;
    }
    point operator-(point b){
        return point(x-b.x,y-b.y);
    }
    point operator+(point b){
        return point(x+b.x,y+b.y);
    }
    point operator*(double b){ //数乘
        return point(x*b,y*b);
    }
    db dis(){ //模长
        return sqrt(x*x+y*y);
    }
}p[N];
int comp0(double x){
    return fabs(x)<=eps?0:(x>0?1:-1);
}
struct line{
    point p,v;//p起点,v终点 ,表示线段
    line(point a,point b): p(a),v(b){}
    double theta;
    bool operator <(line y){
        return comp0(theta-y.theta)==0?comp0((y.v-p)*(v-p))<0:comp0(theta-y.theta)<0;
    }//排序,保证第一关键字是极角,第二关键字是与右边的距离(用叉积判相对关系,在左边的放后面)
};
point inter(line a,line b){//交点  intersection
    point v1=a.v-a.p,v2=b.v-b.p;
    return b.p+v2*(((b.p-a.p)*v1)/(v1*v2));
}//此处默认了没有平行的情况 , 判断线段有没有交点就是判断点是否在线段上,但是半平面交是直线(只是用线段表示)
int out(line a,line b,line k){
    point ins=inter(a,b); //此处要判断的是ins是否在k的右边,画个图
    return comp0((k.v-k.p)*(ins-k.p))<0;
}
int n;
int in(point k,point c,double r){
    return comp0(r-(k-c).dis())>=0;
}
point chrt(point a){
    return point(-a.y,a.x);
}
void randomShuffle(){
    srand(19260817+time(0)); //知道为什么不用STL的吗?
    for (int i=1;i<=n;i++){
        int j=(rand()%n+1926)%n+1;//这里随便rand()%n就行了,但我们要把玄学发扬光大【滑稽】
        swap(p[i],p[j]);
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%lf%lf",&p[i].x,&p[i].y);
    }
    randomShuffle(); //这句很重要
    point c;
    db r=0.0;
    for(int i=1;i<=n;i++){
        if(in(p[i],c,r)) continue;
        c=p[i],r=0;//重新计算
        for(int j=1;j<i;++j){
            if(in(p[j],c,r)) continue;
            r=(p[j]-p[i]).dis()/2.0;
            c=p[i]*0.5+p[j]*0.5;
            for(int k=1;k<j;k++){
                if(in(p[k],c,r)) continue;
                line a=line((p[i]+p[j])*0.5,chrt(p[i]-p[j])+(p[i]+p[j])*0.5);
                line b=line((p[k]+p[j])*0.5,chrt(p[j]-p[k])+(p[j]+p[k])*0.5);
                c=inter(a,b);r=(c-p[i]).dis();
            }
        }
    }
    printf("%.10f\n%.10f %.10f",r,c.x,c.y);
    return 0;
}

后记

我是看这个DALAO的博客学的

应该还有val.3 (学习nekopara

就写一写 定积分基础 / 闵可夫斯基和 / 辛普森积分 (自适应辛普森) 啥的

原文地址:https://www.cnblogs.com/lcyfrog/p/11695145.html

时间: 2024-10-31 18:48:52

计算几何 val.2的相关文章

计算几何 val.1

目录 计算几何 val.1 向量的点积 向量的叉积 一种奇怪的三角剖分求面积 凸包 点绕点旋转 后记 计算几何 val.1 本文并不是入门文章,供有高中数学基础的阅读 主要写一些重要的点和注意事项吧 向量的点积 如果两个向量同向(共线),那么它们的数量积为他们的模长之积. 如果两个向量夹角 \(<90^\circ\) ,那么它们的数量积为正. 如果两个向量夹角 \(=90^\circ\) ,那么他们的数量积为 \(0\) . 如果两个向量夹角 \(>90^\circ\) ,那么它们的数量积为负

计算几何 val.3

目录 计算几何 val.3 自适应辛普森法 定积分 引入 辛普森公式 处理精度 代码实现 模板 时间复杂度 练习 闵可夫斯基和 Pick定理 结论 例题 后记 计算几何 val.3 自适应辛普森法 可以用来求多边形的面积并(圆也行) 定积分 定积分的几何意义是函数的曲线上 \(x\) 的一段区间与 \(x\) 轴围成的曲边梯形的带符号面积 表示法为 \[ \int_{a}^{b} f(x) \mathrm{d} x \] 引入 计算方法: 分成一堆小区间 \[ \int_{a}^{b} f(x)

计算几何中的精度问题

转自:北岛知寒 计算几何头疼的地方一般在于代码量大和精度问题,代码量问题只要平时注意积累模板一般就不成问题了.精度问题则不好说,有时候一个精度问题就可能成为一道题的瓶颈,简直"画龙点睛".这些年的题目基本是朝着越来越不卡精度的方向发展了,但是也不乏一些奇怪的题,另外有些常识不管题目卡不卡,都是应该知道的.今天我就开膛回顾下见过且还有印象的精度问题,由于本人见识和记忆均有限,望各位大神瞄过后不吝补充.另外,为了弥补我匮乏的文思,我可能乱扯些不太相关或者尽人皆知的东西凑数.那么,现在开始.

UESTC 1170 红与蓝 计算几何、贪心、红蓝点对

D - EN TARO Artanis Time Limit:1000MS     Memory Limit:65535KB     64bit IO Format:%lld & %llu Submit Status Practice UESTC 1170 Description 平面上有N个红点和N个蓝点,求红点到蓝点的最近距离 Input 第一行为一个整数N  接下来第N行每行两个整数xi,yi,表示第i个红点的坐标 接下来第N行每行两个整数xi,yi,表示第i个蓝点的坐标(1      

【bzoj1822】[JSOI2010]Frozen Nova 冷冻波 计算几何+二分+网络流最大流

题目描述 WJJ喜欢“魔兽争霸”这个游戏.在游戏中,巫妖是一种强大的英雄,它的技能Frozen Nova每次可以杀死一个小精灵.我们认为,巫妖和小精灵都可以看成是平面上的点. 当巫妖和小精灵之间的直线距离不超过R,且巫妖看到小精灵的视线没有被树木阻挡(也就是说,巫妖和小精灵的连线与任何树木都没有公共点)的话,巫妖就可以瞬间杀灭一个小精灵. 在森林里有N个巫妖,每个巫妖释放Frozen Nova之后,都需要等待一段时间,才能再次施放.不同的巫妖有不同的等待时间和施法范围,但相同的是,每次施放都可以

【bzoj3630】[JLOI2014]镜面通道 对偶图+计算几何+网络流最小割

题目描述 在一个二维平面上,有一个镜面通道,由镜面AC,BD组成,AC,BD长度相等,且都平行于x轴,B位于(0,0).通道中有n个外表面为镜面的光学元件,光学元件α为圆形,光学元件β为矩形(这些元件可以与其他元件和通道有交集,具体看下图).光线可以在AB上任一点以任意角度射入通道,光线不会发生削弱.当出现元件与元件,元件和通道刚好接触的情况视为光线无法透过(比如两圆相切).现在给出通道中所有元件的信息(α元件包括圆心坐标和半径xi,yi,ri,β元件包括左下角和右上角坐标x1,y1,x2,y2

计算几何——精度问题

计算几何中的精度问题(转)(谢谢原创) 计算几何头疼的地方一般在于代码量大和精度问题,代码量问题只要平时注意积累模板 一般就不成问题了.精度问题则不好说,有时候一个精度问题就可能成为一道题的瓶颈,简直“画龙点睛”.这些年的题目基本是朝着越来越不卡精度的方向发展 了,但是也不乏一些%^&%题#$%$^,另外有些常识不管题目卡不卡,都是应该知道的.今天我就开膛回顾下见过且还有印象的精度问题,由于本人 见识和记忆均有限,望各位大神瞄过后不吝补充.另外,为了弥补我匮乏的文思,我可能乱扯些不太相关或者尽人

计算几何精度问题(转)

计算几何头疼的地方一般在于代码量大和精度问题,代码量问题只要平时注意积累模板一般就不成问题了.精度问题则不好说,有时候一个精度问题就可能成为一道题的瓶颈,简直“画龙点睛”.这些年的题目基本是朝着越来越不卡精度的方向发展了,但是也不乏一些%^&%题#$%$^,另外有些常识不管题目卡不卡,都是应该知道的.今天我就开膛回顾下见过且还有印象的精度问题,由于本人见识和记忆均有限,望各位大神瞄过后不吝补充.另外,为了弥补我匮乏的文思,我可能乱扯些不太相关或者尽人皆知的东西凑数.那么,现在开始. 计算几何的精

计算几何中的精度问题(转)

计算几何头疼的地方一般在于代码量大和精度问题,代码量问题只要平时注意积累模板一般就不成问题了.精度问题则不好说,有时候一个精度问题就可能成为一道题的瓶颈,简直“画龙点睛”.这些年的题目基本是朝着越来越不卡精度的方向发展了,但是也不乏一些%^&%题#$%$^,另外有些常识不管题目卡不卡,都是应该知道的.今天我就开膛回顾下见过且还有印象的精度问题,由于本人见识和记忆均有限,望各位大神瞄过后不吝补充.另外,为了弥补我匮乏的文思,我可能乱扯些不太相关或者尽人皆知的东西凑数.那么,现在开始. 计算几何的精