洛谷P4724 【模板】三维凸包

给出空间中 \(n\) 个点 \(p_i\),求凸包表面积。

数据范围:\(1\le n\le 2000\)。


这篇题解因为是世界上最逊的人写的,所以也会有求凸包体积的讲解。


三位向量的运算

  • 模长: 即向量长度,\(|\vec{a}|=\sqrt{x_a^2+y_a^2+z_a^2}\)。

  • 点积: 标量 \(\vec{a}\cdot\vec{b}=|\vec{a}||\vec{b}|\cos<\vec{a},\vec{b}>=x_ax_b+y_ay_b+z_az_b\),为 \(\vec{a}\) 的模长乘以 \(\vec{b}\) 在 \(\vec{a}\) 上的投影的模长。

  • 叉积: 向量 \(\vec{a}*\vec{b}=(y_az_b-z_ay_b,z_ax_b-x_az_b,x_ay_b-y_ax_b)\),模长为平四面积。

上图 \(\vec{AC}*\vec{AB}=\vec{AD}\),\(\vec{AD}\) 垂直 \(\vec{AC}\) 与 \(\vec{AB}\) 的平面,模长为平四面积。


会用到的计算与判定

  • 判断点 \(E\) 在平面 \(ABC\) 上方:

作 \(\vec{AD}=\vec{AC}*\vec{AB}\),用 \(\vec{AE}\cdot \vec{AD}>0\) 来判断 \(\angle DAE<\frac{\pi}{2}\)。

  • 求点 \(E\) 到平面 \(ABC\) 的距离:

距离 \({\rm dist}(E,\triangle ABC)=EG=AF=\frac{\vec{AD}\cdot \vec{AE}}{|\vec{AD}|}=\frac{\vec{AD}\cdot \vec{AE}}{|\vec{AC}*\vec{AB}|}\)。


处理凸包

设凸包为 \(Con\),用逆时针顺序三个点表示一个三角形面。

每加入一个新点 \(p_{new}\) 的时候,把它当作光源照向之前的凸包,将未照到的面留下,加上 \(p_{new}\) 和光影边缘形成的新面。

引用巨佬的图:

判断照不照得到用判定“点 \(E\) 在平面 \(ABC\) 上方”的方法。

判断光影边缘用 \(vis\) 数组。\(vis_{i,j}\) 表示 \((i,j,k)\)(即 \((i,j)\) 逆时针方向上的面)这个面是否照光,如果 \([vis_{i,j}=1]\&\&[vis_{j,i}=0]\),说明 \((i,j)\) 是光影边缘,需加面 \((i,j,p_{new})\)。

重复加点,得到 \(m\) 个 \(Con\) 上的面 \(f_i=(A,C,B)\)。

\[\sum_{i=1}^m S_i=\sum\frac{|\vec{AC}*\vec{AB}|}{2}
\]
\[V_{Con}=\sum \frac{\frac{|\vec{AC}*\vec{AB}|}{2}\cdot {\rm dist}(D,f_i)}{3}
\]

其中 \(D\) 是一个定点,需要在 \(Con\) 内或表面上,可以选 \(p_1\),上面是三棱锥体积计算公式。


时间复杂度 \(\Theta(n^2)\),空间复杂度 \(\Theta(n^2)\)。

每加入一个点,面最多增加 \(2\) 个。

证明:设光影边缘上有 \(n\) 个点,因为每个面是三角形,所以要去掉的面 \(\ge n-2\)(中间可能有点),增加的面数为 \(n\),所以增加的点数 \(\le 2\)。


代码

  • 求表面积
#include <bits/stdc++.h>
using namespace std; //Start
typedef long long ll;
typedef double db;
#define mp(a,b) make_pair(a,b)
#define x first
#define y second
#define be(a) a.begin()
#define en(a) a.end()
#define sz(a) int((a).size())
#define pb(a) push_back(a)
const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f; //Data
const int N=2000;
const db eps=1e-9;
int n,m;
db ans; //Convex
mt19937 orz(time(0));
db reps(){return (1.*(orz()%98)/97-.5)*eps;}
struct point{
db x,y,z;
void shake(){x+=reps(),y+=reps(),z+=reps();}
db len(){return sqrt(x*x+y*y+z*z);}
point operator-(point p){return (point){x-p.x,y-p.y,z-p.z};}
point operator*(point p){return (point){y*p.z-p.y*z,z*p.x-p.z*x,x*p.y-p.x*y};}
db operator^(point p){return x*p.x+y*p.y+z*p.z;}
}a[N];
struct plane{
int v[3];
point flag(){return (a[v[1]]-a[v[0]])*(a[v[2]]-a[v[0]]);}
db area(){return flag().len()/2;}
int see(point p){return ((p-a[v[0]])^flag())>0;}
}f[N],g[N];
int vis[N][N];
void Convex(){
#define ft f[j].v[t]
#define bk f[j].v[(t+1)%3]
f[m++]=(plane){0,1,2},f[m++]=(plane){2,1,0};
for(int i=3;i<n;i++){
int cnt=0,b;
for(int j=0;j<m;j++){
if(!(b=f[j].see(a[i]))) g[cnt++]=f[j];
for(int t=0;t<3;t++) vis[ft][bk]=b;
}
for(int j=0;j<m;j++)
for(int t=0;t<3;t++)
if(vis[ft][bk]&&!vis[bk][ft]) g[cnt++]=(plane){ft,bk,i};
m=cnt;
for(int j=0;j<m;j++) f[j]=g[j];
}
} //Main
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=0;i<n;i++) cin>>a[i].x>>a[i].y>>a[i].z,a[i].shake();
Convex();
for(int i=0;i<m;i++) ans+=f[i].area();
cout.precision(3);
cout<<fixed<<ans<<'\n';
return 0;
}
  • 求体积
#include <bits/stdc++.h>
using namespace std; //Start
typedef long long ll;
typedef double db;
#define mp(a,b) make_pair(a,b)
#define x first
#define y second
#define be(a) a.begin()
#define en(a) a.end()
#define sz(a) int((a).size())
#define pb(a) push_back(a)
const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f; //Data
const int N=2000;
const db eps=1e-9;
int n,m;
db ans; //Convex
mt19937 orz(time(0));
db reps(){return (1.*(orz()%98)/97-.5)*eps;}
struct point{
db x,y,z;
void shake(){x+=reps(),y+=reps(),z+=reps();}
db len(){return sqrt(x*x+y*y+z*z);}
point operator-(point p){return (point){x-p.x,y-p.y,z-p.z};}
point operator*(point p){return (point){y*p.z-p.y*z,z*p.x-p.z*x,x*p.y-p.x*y};}
db operator^(point p){return x*p.x+y*p.y+z*p.z;}
}a[N];
struct plane{
int v[3];
point flag(){return (a[v[1]]-a[v[0]])*(a[v[2]]-a[v[0]]);}
db area(){return flag().len()/2;}
db dist(point p){return fabs(((p-a[v[0]])^flag())/flag().len());}
int see(point p){return ((p-a[v[0]])^flag())>0;}
}f[N],g[N];
int vis[N][N];
void Convex(){
#define ft f[j].v[t]
#define bk f[j].v[(t+1)%3]
f[m++]=(plane){0,1,2},f[m++]=(plane){2,1,0};
for(int i=3;i<n;i++){
int cnt=0,b;
for(int j=0;j<m;j++){
if(!(b=f[j].see(a[i]))) g[cnt++]=f[j];
for(int t=0;t<3;t++) vis[ft][bk]=b;
}
for(int j=0;j<m;j++)
for(int t=0;t<3;t++)
if(vis[ft][bk]&&!vis[bk][ft]) g[cnt++]=(plane){ft,bk,i};
m=cnt;
for(int j=0;j<m;j++) f[j]=g[j];
}
} //Main
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=0;i<n;i++) cin>>a[i].x>>a[i].y>>a[i].z,a[i].shake();
Convex();
for(int i=0;i<m;i++) ans+=f[i].area()*f[i].dist(a[0])/3;
cout.precision(2);
cout<<fixed<<ans<<'\n';
return 0;
}

祝大家学习愉快!

题解-洛谷P4724 【模板】三维凸包的更多相关文章

  1. luogu P4724 模板 三维凸包

    LINK:三维凸包 一个非常古老的知识点.估计也没啥用. 大体上了解了过程 能背下来就背下来吧. 一个bf:暴力枚举三个点 此时只需要判断所有的点都在这个面的另外一侧就可以说明这个面是三维凸包上的面了 ...

  2. 洛谷P3373 [模板]线段树 2(区间增减.乘 区间求和)

    To 洛谷.3373 [模板]线段树2 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.将某区间每一个数乘上x 3.求出某区间每一个数的和 输入输出格式 输入格 ...

  3. 题解-洛谷P5410 【模板】扩展 KMP(Z 函数)

    题面 洛谷P5410 [模板]扩展 KMP(Z 函数) 给定两个字符串 \(a,b\),要求出两个数组:\(b\) 的 \(z\) 函数数组 \(z\).\(b\) 与 \(a\) 的每一个后缀的 L ...

  4. 【AC自动机】洛谷三道模板题

    [题目链接] https://www.luogu.org/problem/P3808 [题意] 给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过. [题解] 不再介绍基础知识了,就是裸的模 ...

  5. 洛谷P3375 [模板]KMP字符串匹配

    To 洛谷.3375 KMP字符串匹配 题目描述 如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置. 为了减少骗分的情况,接下来还要输出子串的前缀数组next.如果 ...

  6. LCT总结——概念篇+洛谷P3690[模板]Link Cut Tree(动态树)(LCT,Splay)

    为了优化体验(其实是强迫症),蒟蒻把总结拆成了两篇,方便不同学习阶段的Dalao们切换. LCT总结--应用篇戳这里 概念.性质简述 首先介绍一下链剖分的概念(感谢laofu的讲课) 链剖分,是指一类 ...

  7. 题解 洛谷P5018【对称二叉树】(noip2018T4)

    \(noip2018\) \(T4\)题解 其实呢,我是觉得这题比\(T3\)水到不知道哪里去了 毕竟我比较菜,不大会\(dp\) 好了开始讲正事 这题其实考察的其实就是选手对D(大)F(法)S(师) ...

  8. 洛谷-P5357-【模板】AC自动机(二次加强版)

    题目传送门 -------------------------------------- 过年在家无聊补一下这周做的几道AC自动机的模板题 sol:AC自动机,还是要解决跳fail边产生的重复访问,但 ...

  9. 题解 洛谷 P3396 【哈希冲突】(根号分治)

    根号分治 前言 本题是一道讲解根号分治思想的论文题(然鹅我并没有找到论文),正 如论文中所说,根号算法--不仅是分块,根号分治利用的思想和分块像 似却又不同,某一篇洛谷日报中说过,分块算法实质上是一种 ...

随机推荐

  1. python读取excel数据转换成字典

    以上面的excel格式,输出字典类型: import xlrddef read_excel_data(): filename = 'E:\学历列表.xls' data = xlrd.open_work ...

  2. 12.java设计模式之代理模式

    基本介绍: 代理模式(Proxy)为一个对象提供一个替身,以控制对这个对象的访问.即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能,想在 ...

  3. Python_opencv库

    1.车牌检测 ''' 项目名称:opencv/cv2 车牌检测 简介: 1.训练级联表 ***.xml [跳过...] 2.用如下代码加载级联表和目标图片识别车牌 注:推荐用anconda安装open ...

  4. hadoop启动脚本

    记录一下一个简单的hadoop启动脚本 就是启动zookeeper集群,hadoop的HDFS和YRAN的脚本 start-cluster.sh 关于关闭的脚本,只需要顺序换一下,然后将start改为 ...

  5. 理解 ASP.NET Core: 处理管道

    理解 ASP.NET Core 处理管道 在 ASP.NET Core 的管道处理部分,实现思想已经不是传统的面向对象模式,而是切换到了函数式编程模式.这导致代码的逻辑大大简化,但是,对于熟悉面向对象 ...

  6. LeetCode 中等题解(1)

    16 最接近的三数之和 Question 给定一个包括 n 个整数的数组 nums 和 一个目标值 target.找出 nums 中的三个整数,使得它们的和与 target 最接近.返回这三个数的和. ...

  7. codeforces 1426F,初学者也能做,div3的最难题

    大家好,欢迎阅读codeforces专题. 今天选择的题目是Div3比赛的最后一题,也是最难的一道题.选这道题的主要原因是帮助大家建立信心,因为有些小伙伴给我反应说之前选择的题目有些难了,觉得自己可能 ...

  8. 深度分析ReentrantLock源码及AQS源码,从入门到入坟,建议先收藏!

    一.ReentrantLock与AQS简介 在Java5.0之前,在协调对共享对象的访问时可以使用的机制只有synchronized和volatile.Java5.0增加了一种新的机制:Reentra ...

  9. 去年去阿里面试,被问到java 多线程,我是这样手撕面试官的

    1.多线程的基本概念 1.1进程与线程 程序:是为完成特定任务,用某种语言编写的一组指令的集合,即一段静态代码,静态对象. 进程:是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,每个程 ...

  10. 全面解析RayFire的动态对象与静态对象

    我们在日常使用RayFire的过程中,接触得比较多的应该就是RayFire的对象设置了.RayFire的对象包含了动态对象.静态对象与休眠对象,其中动态对象.静态对象可以结合动力学.运动学概念设置动作 ...