P4724 【模板】三维凸包 题解
解题思路
增量法求三维凸包。
我们先选择一个面,将这个面作为当前凸包上的一个面,然后考虑不断地添加新的点,并更新凸包,最后用法向量计算每个面地面积,加起来即为答案。
那么如何添加点呢?我们将新的点看作是一个光源,那么光线打到凸包上,会有一些地方照到(两面),有一些地方没有被照到(暗面),同时会有一些边恰好被照到(明暗交界线)。我们将那些恰好被照到的边向光源连边,可以得到若干个新的面,那么,被照到的面肯定会被新的面所包含,我们将这些被照到的面从凸包中删除,然后向凸包中加入新建的面即可。
AC 代码
代码不长,建议自己写一下,不要直接复制题解。
#include<math.h>
#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
#define eps 1e-12
#define N 2005
inline double dis(
const double &x1,
const double &y1,
const double &z1,
const double &x2,
const double &y2,
const double &z2
){
double Ox=(x1-x2)*(x1-x2);
double Oy=(y1-y2)*(y1-y2);
double Oz=(z1-z2)*(z1-z2);
return sqrt(Ox+Oy+Oz);
}
struct Point{
double x;
double y;
double z;
//生成一个0~1的随机数
inline double Rand(){
double _1=rand();
double _2=RAND_MAX;
return _1/_2;
}
//生成微小的变化
inline double RandEps(){
double RAND=Rand();
return (RAND-0.5)*eps;
}
//对向量进行微笑扰动
inline void Shake(){
x+=RandEps();
y+=RandEps();
z+=RandEps();
}
//向量减
inline Point operator -(
const Point &b
) const {
return {x-b.x,y-b.y,z-b.z};
}
//向量加
inline Point operator +(
const Point &b
) const {
return {x+b.x,y+b.x,z+b.z};
}
//向量叉乘
inline Point operator *(
const Point &b
) const {
Point c;
c.x=y*b.z-b.y*z;
c.y=z*b.x-b.z*x;
c.z=x*b.y-b.x*y;
return c;
}
//向量点积
inline double operator ^(
const Point &b
) const {
double X=x*b.x;
double Y=y*b.y;
double Z=z*b.z;
return X+Y+Z;
}
inline double len(){
return sqrt(x*x+y*y+z*z);
}
}a[N];
inline double dis(
const Point &A,
const Point &B
){
double x1=A.x,x2=B.x;
double y1=A.y,y2=B.y;
double z1=A.z,z2=B.z;
return dis(
x1,y1,z1,
x2,y2,z2
);
}
struct Face{
//三个点确定一个面
int p[3];
//求平面法向量
inline Point Normal(){
Point x=a[p[1]]-a[p[0]];
Point y=a[p[2]]-a[p[1]];
return x*y;
}
//判断一个点是否在平面上方
inline bool Above(
const Point &b
){
Point x=b-a[p[0]];
Point y=Normal();
return (x^y)>=0;
}
//求平面面积
inline double Area(){
Point x=Normal();
return x.len()*0.5;
}
}p[N],sta[N];
int n,m,cnt;
bool vis[N][N];
//增量法求凸包
inline void Convex(){
//先放入一个平面
//由于向量是有方向的
//所以按不同顺序放两个
p[m++]={0,1,2};
p[m++]={2,1,0};
//枚举每一个点,将这个点想象为一个灯泡
//能被灯光照到的点肯定是在凸包内部的
//剩余的点是凸包上的点
for(
int i=3;
i<n;++i
){
//集合清零
cnt=0;
//枚举每个当前凸包上的平面
//判断是否能被照到
//判断方式为是否在这个平面下方
for(
int j=0;
j<m;++j
){
//判断第i个点是否在j平面上方
bool flag=p[j].Above(a[i]);
//如果不在,说明还是凸包上的点
//那么直接加入临时存平面的集合
if(!flag)
sta[cnt++]=p[j];
//更新所有边,标记是否被照到
for(
int k=0;
k<3;++k
){
int now=p[j].p[k];
int nxt=p[j].p[(k+1)%3];
vis[now][nxt]=flag;
}
}
//枚举所有边,如果这个边刚好被照到
//即为明暗两面交界处的边
//那么新添加一个和当前点构成的面
for(
int j=0;
j<m;++j
){
for(
int k=0;
k<3;++k
){
int now=p[j].p[k];
int nxt=p[j].p[(k+1)%3];
if(vis[now][nxt])
if(!vis[nxt][now])
sta[cnt++]={now,nxt,i};
}
}
//更新答案的面集
//将集合中存储的所有没有照到的面
//以及新添加的面全部赋给当前凸包面集
m=cnt;
for(
int j=0;
j<m;++j
) p[j]=sta[j];
}
}
inline void init(){
scanf("%d",&n);
for(
int i=0;
i<n;++i
){
scanf("%lf",&a[i].x);
scanf("%lf",&a[i].y);
scanf("%lf",&a[i].z);
a[i].Shake();
}
}
inline void print(){
double ans=0.0;
for(
int i=0;
i<m;++i
) ans+=p[i].Area();
printf("%.3lf",ans);
}
signed main(){
init();
Convex();
print();
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下