计算几何 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

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

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

  1. 计算几何 val.3

    目录 计算几何 val.3 自适应辛普森法 定积分 引入 辛普森公式 处理精度 代码实现 模板 时间复杂度 练习 闵可夫斯基和 Pick定理 结论 例题 后记 计算几何 val.3 自适应辛普森法 可 ...

  2. 计算几何 val.1

    目录 计算几何 val.1 向量的点积 向量的叉积 一种奇怪的三角剖分求面积 凸包 点绕点旋转 后记 计算几何 val.1 本文并不是入门文章,供有高中数学基础的阅读 主要写一些重要的点和注意事项吧 ...

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

    http://www.cnblogs.com/acsmile/archive/2011/05/09/2040918.html 计算几何头疼的地方一般在于代码量大和精度问题,代码量问题只要平时注意积累模 ...

  4. POJ 1066 Treasure Hunt(计算几何)

    题意:给出一个100*100的正方形区域,通过若干连接区域边界的线段将正方形区域分割为多个不规则多边形小区域,然后给出宝藏位置,要求从区域外部开辟到宝藏所在位置的一条路径,使得开辟路径所需要打通的墙壁 ...

  5. POJ 2318 TOYS(计算几何)

    题目大意:有一个矩形盒子,盒子里会有一些木块线段,并且这些线段是按照顺序给出的,有n条线段,把盒子分层了n+1个区域,然后有m个玩具,这m个玩具的坐标是已知的,问最后每个区域有多少个玩具 解题思路:因 ...

  6. TOYS - POJ 2318(计算几何,叉积判断)

    题目大意:给你一个矩形的左上角和右下角的坐标,然后这个矩形有 N 个隔板分割成 N+1 个区域,下面有 M 组坐标,求出来每个区域包含的坐标数.   分析:做的第一道计算几何题目....使用叉积判断方 ...

  7. POJ 1556 计算几何+最短路

    代码1: #include<iostream> #include<stdio.h> #include<string> #include<string.h> ...

  8. 【BZOJ5316】[JSOI2018]绝地反击(网络流,计算几何,二分)

    [BZOJ5316][JSOI2018]绝地反击(网络流,计算几何,二分) 题面 BZOJ 洛谷 题解 很明显需要二分一个答案. 那么每个点可以确定的范围就是以当前点为圆心,二分出来的答案为半径画一个 ...

  9. POJ - 2031 Building a Space Station(计算几何+最小生成树)

    http://poj.org/problem?id=2031 题意 给出三维坐标系下的n个球体,求把它们联通的最小代价. 分析 最小生成树加上一点计算几何.建图,若两球体原本有接触,则边权为0:否则边 ...

随机推荐

  1. C语言|博客作业10

    问题 回答 C语言 博客作业10 这个作业要求在哪里 作业要求 我在这个课程的目标是 熟练循环语句的用法 这个作业在哪个具体方面帮助我实现目标 pta作业 参考文献 <C语言程序设计> 1 ...

  2. python-布隆过滤器

    在学习redis过程中提到一个缓存击穿的问题, 书中参考的解决方案之一是使用布隆过滤器, 那么就有必要来了解一下什么是布隆过滤器.在参考了许多博客之后, 写个总结记录一下. 一.布隆过滤器简介 什么是 ...

  3. ThinkPHP5——模型关联(一对一关联)

    定义 定义一对一关联使用了hasOne,hasOne方法的参数包括: hasOne('关联模型名','外键名','主键名',['模型别名定义'],'join类型'); 下面定义一个用户表,公司给每个用 ...

  4. 深入理解跳跃链表在Redis中的应用

    0.前言 前面写了一篇关于跳表基本原理和特性的文章,本次继续介绍跳表的概率平衡和工程实现,跳表在Redis.LevelDB.ES中都有应用,本文以Redis为工程蓝本,分析跳表在Redis中的工程实现 ...

  5. 【重磅资料】ArchSummit全球架构师峰会·2019华为云技术专场资料下载

    ArchSummit全球架构师峰会是InfoQ中国团队推出的重点面向高端技术管理者.架构师的技术会议,54%参会者拥有8年以上工作经验.ArchSummit聚焦业界强大的技术成果,秉承"实践 ...

  6. 阿里巴巴的 Kubernetes 应用管理实践经验与教训

    作者 | 孙健波(天元)  阿里巴巴技术专家 导读:本文整理自孙健波在 ArchSummit 大会 2019 北京站演讲稿记录.首先介绍了阿里巴巴基于 Kubernetes 项目进行大规模应用实践过程 ...

  7. Xcode10.0: NO BUNDLE URL PRESENT

    目录 解决方案 1.删除build, 重新运行, 没有work 2.删除node_modules, npm i, 重新运行, 没有work 3.删除端口占用 4.代理设置, 可能work了 解决方案 ...

  8. 商品类目和商品大广告的Redis缓存

    (dubbo)主要的实现类: 商品类目的Redis缓存 com.bjsxt.ego.portal.service.impl.PortalItemCatServiceImpl package com.b ...

  9. 最全的防火墙(firewalld)

    第1章  防火墙的介绍 1.1  防火墙的介绍 1.1.1 概念 动态管理防火墙服务(图形界面和linux界面都可以实现) 支持不同防火墙的区域信息 属于传输层次的防火墙 1.1.2 防火墙的默认规则 ...

  10. 关于 Kafka 的一些面试题目

    上周客串了一下面试官,在这里就简单记录一下期间我问到的一些关于 Kafka 的面试题目,这些都是我平时在学习 Kafka 的一些总结要点. 谈谈你对 kafka 的整体认识? 问这个问题主要是想知道面 ...