我必须堆叠圆柱体,以便获得最大的表面积。
表面积是堆叠圆柱体的可见部分。
我必须从给定的n个圆柱中选择k个圆柱以形成堆栈。
n <=1000。并且k小于n。可以放置圆柱体,而不必考虑半径(我们可以将较宽的圆柱体放在较窄的圆柱体顶部)。仅可见表面积应该最大。 (我们认为隐藏了最低圆柱的底部。)
我的方法是首先计算所有圆柱体的表面积并将其排序。然后使用半径和高度,我可以创建另一个列表,该列表将根据半径和高度进行排序。但是,在某些情况下,这种方法无法获得所选圆柱体的最大表面积。
我如何以其他方式解决此问题?
答案 0 :(得分:1)
假设我们将改变相对半径,以使较窄的圆柱体位于较宽的圆柱体之间,从而最大程度地减少覆盖的顶部和底部表面积。然后,为了计算总表面积,我们只减去较窄圆柱体的顶面和底面面积的两倍。
但是,如果我们保证在两者之间选择较窄的圆柱,则选定的圆柱(底部除外)的顺序无关紧要(除了强制使用“较窄的中间限制”)。这是因为唯一的影响变量是选择了哪些圆柱。
此外,对于每个圆柱体选择,至少都有一个明显的最佳安排:将最大的狭窄圆柱体((较大)中位数)放在底部并交替放置。我们保证从总表面积中减去所选列表中最低的水平表面积。
Given horizontal surface areas:
1 2 3 4 5
3 1 5 2 4 is the same as
3 2 5 1 4 but larger than
2 4 3 5 1 because in the former
we subtract 3 + 4*2 + 4*1 from the
total, whereas in the latter, we
subtract 3*2 + 4*3 + 2*1
给定k
个圆柱,最佳布置将减去最低的k/2
两倍的水平表面积,除了(较大的)中位数仅减去一个水平表面。
如果我们添加另一个圆柱体x
,其半径不小于所有其他圆柱体,那么我们可以
(1) replace a cylinder, y, that has a radius
greater than the (larger) median
In this case, we increase the
total surface area by
total_area(x) - total_area(y)
或
(2) replace a cylinder, y, that has a radius
lower than or equal to the (larger) median, m,
in which case the median will
shift to the next higher radius, m1.
If we're replacing m, we'll add
total_area(x)
- (total_area(m) - horizontal_area(m)) // removed
- horizontal_area(m1) // goes to bottom
otherwise, we will add
total_area(x)
- 3*horizontal_area(m) // no longer on bottom
- horizontal_area(m1) // goes to bottom
+ 4*horizontal_area(y) - vertical_area(y) // removed
如果这种贪婪的优先级队列有效,我们可以通过首先增大半径进行排序,然后尝试遍历该列表,尝试将当前队列中的所有元素替换为候选对象,从而获得O(nk)时间例程。否则,也许我们可以将此处的信息用于其他优化。
我想添加user3386109的评论(从此答案下方),这可能会进一步完善该方向:
在k为奇数的情况下查看问题的另一种方法。具有中值半径的圆柱体在底部。比中位数宽的圆柱体会同时增加顶部和底部面积。中间圆柱体增加了其顶部面积。圆柱体比中位数要窄,减去顶部和底部面积。因此,示例(3 1 4 2 5
)中的总水平面积为(5 + 5)+(4 + 4)+(3)-(2 + 2)-(1 + 1)= 15
更换气缸时,有以下三种可能性:1)更换中位数; 2A)用另一个宽气缸更换宽气缸; 2B)用另一个窄气缸更换窄气缸。在情况2A中,卸下总面积最小的宽圆柱体(侧面+顶部+底部)。在情况2B中,删除减法面积最小的窄圆柱体(上下)。
通常,我认为此答案中提出的方法是正确的方法。取前k个气瓶,并进行排列。然后考虑是否可以使用每个新的气缸来改善总面积,从而替换现有的气缸之一。
答案 1 :(得分:0)
假定:半径较大的圆柱体不能堆叠在半径较小的圆柱体上,首先需要按半径对它们进行排序。
考虑在另一个气缸(C2)的顶部增加气缸(C1)。 C1的顶部表面积被C2的底部表面积覆盖。同时,C2的顶部等于其底部。因此,在决定采用哪种气缸时,我们只需要考虑侧面表面积即可。
有一个例外:对于最低的圆柱体,也需要考虑其顶部表面积!
我们希望在底部具有最大表面积的圆柱体,并在其顶部仍然有k个圆柱体。因此,我们需要从已排序的圆柱中选择一个(总计-k + 1),该圆柱具有最大的总表面积,这将成为底部。 接下来,我们需要按k-1圆柱体的最大侧面面积排序,这就是您的答案。
以下示例:每10个气缸中取4个。
从7个最大半径的圆柱中选择底部圆柱,它是最大表面积(1)的圆柱。现在,选择侧面面积最大的3(2、3和4)。堆放气瓶!
使用C#代码:
public class Cylinder
{
private double radius, height;
public Cylinder(double Radius, double Height)
{
radius = Radius;
height = Height;
}
public double Radius { get { return radius; } }
public double Height { get{return height; } }
public double SideArea { get { return radius * height * Math.PI; } }
public double TotalArea { get { return SideArea + Math.PI * radius * radius; } }
}
class Program
{
static void Main()
{
List<Cylinder> stack = new List<Cylinder>();
stack.Add(new Cylinder(2, 356));
stack.Add(new Cylinder(23, 0.25));
stack.Add(new Cylinder(3, 8));
stack.Add(new Cylinder(10, 2));
stack.Add(new Cylinder(22, 3));
stack.Add(new Cylinder(4, 40));
stack.Add(new Cylinder(2.5, 2));
stack.Add(new Cylinder(21, 2));
stack.Add(new Cylinder(3.5, 32));
stack.Add(new Cylinder(7, 1));
List<Cylinder> orderedStack = stack.OrderByDescending(c => c.Radius).ThenByDescending(c=>c.Height).ToList();
int k = 4;
Cylinder Bottom = orderedStack.Take(orderedStack.Count + 1 - k).OrderByDescending(c => c.TotalArea).First();
int index = orderedStack.IndexOf(Bottom);
List<Cylinder> stackedCylinders = orderedStack.Skip(index + 1).OrderByDescending(c => c.SideArea).Take(k - 1).OrderByDescending(c=>c.Radius).ToList();
stackedCylinders.Insert(0,Bottom);
}
}
答案 2 :(得分:0)
此答案基于גלעדברקן的答案。因此,如果您喜欢这个答案,请同时投票给他。
假设我们已经知道我们将要使用的k
个圆柱体。然后,对于给定的顺序,我们可以计算出总的可见表面积为:
k k-1
A_visible = Σ A_(total,i) - A_(baseArea, bottomMost) - Σ A_(hidden,i)
i=0 i=0
A_(total,i)
是圆柱体i
的总表面积,A_(hidden,i)
是由于界面i
的阻塞而被隐藏的表面积(我们的接口比圆柱体),而A_(baseArea, bottomMost)
是最底部圆柱体的底面积(顶部和底部底部)。隐藏的表面是
A_(hidden,i) = 2 * min(A_(base,i), A_(base,i+1))
A_(base,i)
是圆柱体i
的基础区域。也就是说,两个圆柱体都隐藏了接口处较小的基部区域。
所以,如果我们想最大化一组已知圆柱体
k k-1
max A_visible = max Σ A_(total,i) - min[ A_(baseArea, bottomMost) + Σ 2 * min(A_(base,i), A_(base,i+1)) ]
i=0 i=0
然后我们可以删除第一项,而只需最小化第二项。因此,我们希望找到一个顺序,其中每个界面加上最底部的隐藏表面的总最小底面积最小。正如גלעדברקן已经指出的那样,可以通过在宽和窄圆柱之间交替来最大程度地减少这种情况。但是,我们仍然不知道最底部的圆柱是宽圆柱还是窄圆柱。让我们考虑偶数k
的两种情况:
k=7 | k=8
case A case B | case C case D
|
7 | xxx xxxxxxxx
6 xxxxxxxx xxx | xxxxxxx xxx
5 xxx xxxxxx | xxx xxxxx
4 xxxxx xx | xxxxxxx xx
3 xx xxxxx | xx xxxxxxx
2 xxxxxxx xxx | xxxxxx xxx
1 xxx xxxxxxx | xxx xxxxxx
0 xxxxxxx xx | xxxxxxxx xx
---------------------------------------------
这四种情况的免赔额为(Ai
代表A_(base,i)
):
case A: A0 + 4 * (A1 + A3 + A5)
case B: 3 * A0 + 4 * (A2 + A4) + 2 * A6
case C: A0 + 4 * (A1 + A3 + A5) + 2 * A7
case D: 3 * A0 + 4 * (A2 + A4 + A6)
在每种情况下,我们都希望找到一个可扣除面积最小的作业。因此,我们按其基本面积对可用圆柱进行排序,并开始将最小的圆柱分配给系数为4的槽,将下一个分配给系数为3的槽,依此类推。对于有序基区域Bi
(B0 <= B1 <= B2 ...
)的序列,我们得到以下系数:
case A case B | case C case D
B0 4 4 | 4 4
B1 4 4 | 4 4
B2 4 3 | 4 4
B3 1 2 | 2 3
B4 | 1
让我们比较一下k
为奇数的两种情况
case A < case B
<=> 4 * B2 + B3 < 3 * B2 + 2 * B3
<=> B2 < B3
因为Bi
是有序的,所以总是这样。因此,对于奇数k
,情况A始终是最优的。我们有(k - 1)/2
的{{1}}系数和4
的一个系数。
现在偶数情况:
1
从来没有这样。因此,对于 case C < case D
<=> 2 * B3 + B4 < 3 * B3
<=> B4 < B3
系数为D
和一个系数为k
的偶数k / 2 - 1
,情况4
始终是最优的。
因此,我们将圆柱体分为窄圆柱体,宽圆柱体和中间圆柱体(将成为底部圆柱体)。然后,最大可见面积为(以下为奇数3
;为偶数k
替换最后一个系数)
k
现在,我们想找到要选择的 k
max A_visible = Σ A_(total,i) - A_(base,middle) - Σ 4 * A_(base,j)
i=0 j ∈ narrow
个圆柱体。让我们重新编写上面的等式,以使总和遍及单独的圆柱范围:
k
现在,我们如何找到圆柱体?这都是关于中间元素的。因此,让我们首先按其基础区域对max A_visible = Σ A_(total,j) + A_(total,middle) - A_(base,middle) + Σ [A_(total,j) - 4 * A_(base,j)]
j ∈ wide j ∈ narrow
圆柱进行排序。如果我们知道中间元素,则窄集合中的圆柱必须在中间元素的左边,而宽集合中的圆柱必须在中间元素的右边。为了找到实际的圆柱,我们只需找到使n
(对于宽集合)或A_(total,j)
(对于窄集合)的总和最大化的集合。
我们将每个元素视为中间元素,然后找到最大值。这可以通过从两侧进行两次后续遍历来逐步完成。对于第一个遍历,初始化中间元素,以使窄集具有恰好所需的条目数。然后,以迭代方式进一步推动中间元素,并检查是否用新条目替换任何现有条目会增加面积(评估A_(total,j) - 4 * A(base,j)
并将最大的元素保留在正确大小的堆中)。将每个中间元素的最大值存储在列表中(即,如果我们假设元素6是中间元素,则列表条目6将保留最大面积)。然后,从另一侧对宽集执行相同的操作(仅评估A_(total,j) - 4 * A_(baseArea, j)
)并生成第二个列表。最后,我们需要找到正确的中间元素。为此,我们同时遍历两个列表并找到最大A_(total, j)
。这是您的最终结果。同样,即使是narrowList[i] + A(total,i) - A_(base,i) + wideList[i]
,也需要调整中间元素的系数。
这是一些示例C ++代码。该算法的时间复杂度为 O(n log n),如果使用线性排序算法或圆柱体,则可以降低为 O(n log k)已经排序。
k
示例输出:
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
#include <functional>
struct Cylinder
{
double baseArea, totalArea;
};
std::ostream& operator<<(std::ostream& stream, const Cylinder& c)
{
return stream << "(" << c.baseArea << ", " << c.totalArea << ")";
}
typedef std::pair<double, int> MaxEntry; //holds pairs of contributed area and cylinder index
typedef std::priority_queue<MaxEntry, std::vector<MaxEntry>, std::greater<MaxEntry>> MaxElementsQueue;
//Calculates the maximum contributing area for a set of elementsInSet cylinders in the range [startIndex, endIndex] (both inclusive).
// areaFunction(i): calculates the contributing area for cylinder i
// reportAreaCallback(i, area, elements): reports the maximum area for a set with cylinders in the range [startIndex, i - direction] (i is the middle element)
void calculateMaxAreas(int startIndex, int endIndex, int direction, int elementsInSet, std::function<double(int)>&& areaFunction, std::function<void(int, double, const MaxElementsQueue& elements)>&& reportAreaCallback)
{
MaxElementsQueue maxElements;
double totalSetArea = 0;
//initialize the queue with elementsInSet elements
int firstMiddleElement = startIndex + direction * elementsInSet;
for (int i = startIndex; i * direction < firstMiddleElement * direction; i += direction)
{
double area = areaFunction(i);
maxElements.push(std::make_pair(area, i));
totalSetArea += area;
}
reportAreaCallback(firstMiddleElement, totalSetArea, maxElements);
for (int i = firstMiddleElement + direction; i * direction <= endIndex * direction + 1; i += direction)
{
//try to replace the cylinder with smallest contribution with another cylinder
double area = areaFunction(i - direction);
auto& smallestEntry = maxElements.top();
if (area > smallestEntry.first)
{
totalSetArea -= smallestEntry.first;
maxElements.pop();
maxElements.push(std::make_pair(area, i - 1));
totalSetArea += area;
}
reportAreaCallback(i, totalSetArea, maxElements);
}
}
int main()
{
//Input
int k = 3;
std::vector<Cylinder> availableCylinders;
availableCylinders.push_back({ 20, 100 });
availableCylinders.push_back({ 30, 70 });
availableCylinders.push_back({ 40, 120 });
availableCylinders.push_back({ 50, 180 });
//Derived information
int narrowCylinders, wideCylinders, middleCylinderCoefficient;
if (k % 2 == 0) //k is even
{
narrowCylinders = k / 2 - 1;
middleCylinderCoefficient = 3;
}
else
{
narrowCylinders = (k - 1) / 2;
middleCylinderCoefficient = 1;
}
wideCylinders = k - 1 - narrowCylinders;
//calculate area lists for narrow set and wide set
//we ignore some indices to make the code more readable
std::vector<double> narrowCylinderSetAreas(availableCylinders.size());
std::vector<double> wideCylinderSetAreas(availableCylinders.size());
//sort cylinders by base area
std::sort(availableCylinders.begin(), availableCylinders.end(),
[](const Cylinder& left, const Cylinder& right) { return left.baseArea < right.baseArea; });
//calculate narrow set areas
auto narrowAreaFunction = [&](int i) { return availableCylinders[i].totalArea - 4 * availableCylinders[i].baseArea; };
calculateMaxAreas(0, availableCylinders.size() - 1 - wideCylinders - 1, 1, narrowCylinders,
narrowAreaFunction,
[&](int i, double area, const MaxElementsQueue& queue) { narrowCylinderSetAreas[i] = area; });
//calculate wide set areas
auto wideAreaFunction = [&](int i) { return availableCylinders[i].totalArea; };
calculateMaxAreas(availableCylinders.size() - 1, narrowCylinders + 1, -1, wideCylinders,
wideAreaFunction,
[&](int i, double area, const MaxElementsQueue& queue) { wideCylinderSetAreas[i] = area; });
//now find the optimal middle cylinder
double maxArea = -1;
int maxAreaIndex;
for (int i = narrowCylinders; i < availableCylinders.size() - wideCylinders; ++i)
{
double totalArea = wideCylinderSetAreas[i] + availableCylinders[i].totalArea - middleCylinderCoefficient * availableCylinders[i].baseArea + narrowCylinderSetAreas[i];
if (totalArea > maxArea)
{
maxArea = totalArea;
maxAreaIndex = i;
}
}
std::cout << "Maximum visible area: " << maxArea << " with bottom cylinder " << availableCylinders[maxAreaIndex] << std::endl;
//reconstruct the elements in the wide and narrow sets
calculateMaxAreas(0, maxAreaIndex - 1, 1, narrowCylinders, narrowAreaFunction,
[&](int i, double area, const MaxElementsQueue& elements)
{
if (i == maxAreaIndex)
{
std::cout << "Cylinders in narrow set: ";
auto elementsCopy = elements;
while (!elementsCopy.empty())
{
std::cout << availableCylinders[elementsCopy.top().second] << " ";
elementsCopy.pop();
}
std::cout << std::endl;
}
});
calculateMaxAreas(availableCylinders.size() - 1, maxAreaIndex + 1, -1, wideCylinders, wideAreaFunction,
[&](int i, double area, const MaxElementsQueue& elements)
{
if (i == maxAreaIndex)
{
std::cout << "Cylinders in wide set: ";
auto elementsCopy = elements;
while (!elementsCopy.empty())
{
std::cout << availableCylinders[elementsCopy.top().second] << " ";
elementsCopy.pop();
}
std::cout << std::endl;
}
});
}