题解 P2287 【[HNOI2004]最佳包裹】

SuperJvRuo

2018-06-13 19:04:25

Solution

显然这道题是要求三维凸包的表面积,常用的方法是卷包裹法和增量法,~~我都不会~~。此题数据范围较小,可以直接暴力$O(n^4)$水过。精通计算几何的大佬可以介绍一下卷包裹法和增量法。 $O(n^3)$枚举点集中的三个点,再用$O(n)$扫一遍其他点,如果其他点都在这三个点形成的面的同一侧,那么这个面就在凸包上。 那么如何判断点$P$在面$ABC$的哪一侧呢?只要求出面$ABC$的一个法向量(叉积即可),再用点积算一下$\vec{AP}$与法向量的夹角余弦值即可。余弦为正的点在一侧,余弦为负的在另一侧。 但是只这么做会WA的很惨,因为会有四点共面的情况存在。你需要让点抖一抖来避免共面的出现。 然后,见证暴力的奇迹吧: ``` #include<cstdio> #include<cmath> #include<cstdlib> #define EPS 1e-10 //设EPS为1e-9时爆精度只得了75分,可能是脸黑 struct Point { double x,y,z; }point[105]; int n; typedef Point Vector; double rand01() { return rand()/(double)RAND_MAX; } double randeps() { return (rand01()-0.5)*EPS; } Vector add_noise(Vector p) { return (Vector){p.x+randeps(),p.y+randeps(),p.z+randeps()}; }//生成一个一定EPS内的抖动 Vector operator - (Vector a,Vector b) { return (Vector){a.x-b.x,a.y-b.y,a.z-b.z}; }//减法 double mod(Vector a) { return sqrt(a.x*a.x+a.y*a.y+a.z*a.z); }//模长 double Dot(Vector a,Vector b) { return a.x*b.x+a.y*b.y+a.z*b.z; }//点积,也就是数量积 Vector Cross(Vector a,Vector b) { return (Vector){a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x}; }//叉积,也就是向量积 double area(Vector a,Vector b) { return 0.5*mod(Cross(a,b)); }//面积,叉积模长的一半 bool check(int p1,int p2,int p3) { double head=0,now; Vector normal=Cross(point[p1]-point[p2],point[p2]-point[p3]);//将叉积作为法向量 for(int i=0;i<n;++i) { if(i!=p1&&i!=p2&&i!=p3) { if((now=Dot(point[i]-point[p1],normal))*head<0) {//点积与余弦值一定同号 return false; } else { head=now; } } } return true; } int main() { scanf("%d",&n); double res=0; for(int i=0;i<n;++i) { scanf("%lf %lf %lf",&point[i].x,&point[i].y,&point[i].z); point[i]=add_noise(point[i]);//让点抖动一下 } for(int i=0;i<n;++i) { for(int j=i+1;j<n;++j) { for(int k=j+1;k<n;++k) { if(check(i,j,k)) {//所有的点都在一侧 res+=area(point[i]-point[j],point[j]-point[k]);//就加上这个面的面积 } } } } printf("%.6lf",res); return 0; } ```