2119. 最佳包裹
题目链接
2119. 最佳包裹
发强公司生产了一种金属制品,是由一些笔直的金属条连接起来的,金属条和别的金属条在交点上被焊接在了一起。
现在由于美观需要,在这个产品用一层特殊的材料包裹起来。
公司为了节约成本,希望消耗的材料最少(不计裁剪时的边角料的损失)。
编程,输入包括该产品的顶点的个数,以及所有顶点的坐标;请计算出包裹这个产品所需要的材料的最小面积。
结果要求精确到小数点后第六位(四舍五入)。
输入格式
输入文件由若干行组成:
第 \(1\) 行是一个整数 \(n\),表示顶点的个数;第 \(2\) 行到第 \(n+1\) 行,每行是 \(3\) 个实数 \(x_i,y_i,z_i\),表示第 \(i\) 个顶点的坐标。
每个顶点的位置各不相同。
输出格式
输出文件只有一个实数,表示包裹一个该产品所需的材料面积的最小值。
数据范围
\(4 \le n \le 100\)
输入样例:
4
0 0 0
1 0 0
0 1 0
0 0 1
输出样例:
2.366025
解题思路
三维凸包
增量法:假设已经求出 \(1\sim i-1\) 的三维凸包,求解 \(1\sim i\) 的三维凸包相当于将 \(i\) 这个点当作光源,照射 \(1\sim i-1\) 构成的凸包,照射后,处于背面的平面一定要留下来,同时那些边缘线也需要留下来与 \(i\) 这个点构成平面,\(\color{red}{如何判断边缘线?}\)边缘线是正面和背面的交集,判断所有平面的直线是否同时处在正面和背面即可
另外,还需要处理四点共面的情况,\(\color{red}{为什么?}\)在判断边缘线时,如果当前处理的点正好在四点共面的其中一个点上,则该边缘线可能判断不出来,不妨将所有点随机抖动一个微小的范围,这样四点共面的概率就会很小
由多面体的欧拉定理:\(顶点数-棱长数+表面数=2\),设顶点数为 \(a\),棱长数为 \(b\),表面数为 \(c\),对于一个平面来说其有三个点,即 \(a=3c\),而对于一条边来说其有两个点,即 \(a=2b\),即 对于一个平面都是三角形的多面体来说 \(b\) 和 \(c\) 的关系为 \(3c=2b\),代入公式得 \(b=3n-6,c=2n-4\),故:
- 时间复杂度:\(O(n^2)\)
代码
// Problem: 最佳包裹
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/2121/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// %%%Skyqwq
#include <bits/stdc++.h>
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=105;
const double eps=1e-10;
int n,m;
bool g[N][N];
double rand_eps()
{
return ((double)rand()/RAND_MAX-0.5)*eps;
}
struct Point
{
double x,y,z;
void shake()
{
x+=rand_eps(),y+=rand_eps(),z+=rand_eps();
}
Point operator-(Point o)
{
return {x-o.x,y-o.y,z-o.z};
}
Point operator*(Point o)
{
return {y*o.z-z*o.y,z*o.x-x*o.z,x*o.y-y*o.x};
}
double operator&(Point o)
{
return x*o.x+y*o.y+z*o.z;
}
double len()
{
return sqrt(x*x+y*y+z*z);
}
}point[N];
struct Plane
{
int v[3];
Point norm()
{
return (point[v[1]]-point[v[0]])*(point[v[2]]-point[v[0]]);
}
double area()
{
return norm().len()/2;
}
bool above(Point a)
{
return ((a-point[v[0]])&norm())>=0;
}
}plane[N],np[N];
void get_convex_3d()
{
plane[++m]={1,2,3};
plane[++m]={3,2,1};
for(int i=4;i<=n;i++)
{
int cnt=0;
for(int j=1;j<=m;j++)
{
bool t=plane[j].above(point[i]);
if(!t)np[++cnt]=plane[j];
for(int k=0;k<3;k++)
g[plane[j].v[k]][plane[j].v[(k+1)%3]]=t;
}
for(int j=1;j<=m;j++)
for(int k=0;k<3;k++)
{
int a=plane[j].v[k],b=plane[j].v[(k+1)%3];
if(g[a][b]&&!g[b][a])np[++cnt]={a,b,i};
}
m=0;
for(int i=1;i<=cnt;i++)plane[++m]=np[i];
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%lf%lf%lf",&point[i].x,&point[i].y,&point[i].z),point[i].shake();
get_convex_3d();
double res=0;
for(int i=1;i<=m;i++)res+=plane[i].area();
printf("%.6lf",res);
return 0;
}