堆叠气缸以获取最大表面积

时间:2018-11-17 02:11:07

标签: algorithm

我必须堆叠圆柱体,以便获得最大的表面积。 表面积是堆叠圆柱体的可见部分。
我必须从给定的n个圆柱中选择k个圆柱以形成堆栈。

n <=1000。并且k小于n。可以放置圆柱体,而不必考虑半径(我们可以将较宽的圆柱体放在较窄的圆柱体顶部)。仅可见表面积应该最大。 (我们认为隐藏了最低圆柱的底部。)

我的方法是首先计算所有圆柱体的表面积并将其排序。然后使用半径和高度,我可以创建另一个列表,该列表将根据半径和高度进行排序。但是,在某些情况下,这种方法无法获得所选圆柱体的最大表面积。

我如何以其他方式解决此问题?

3 个答案:

答案 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

    < / li>
  • 更换气缸时,有以下三种可能性: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)。堆放气瓶!

enter image description here

使用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的槽,依此类推。对于有序基区域BiB0 <= 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;
        }
    });
}