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;
}
posted @ 2022-11-23 15:52  zyy2001  阅读(7)  评论(0编辑  收藏  举报