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();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值