13

所以这是一个问题:

在一个聚会中,有 n 个不同口味的蛋糕,体积分别为 V1、V2、V3 ... Vn。需要将他们分成在聚会中的 K 个人,这样

  • 党的所有成员都得到相同体积的蛋糕(比如 V,这是我们正在寻找的解决方案)

  • 每个会员只能得到一个单一口味的蛋糕(您不能将不同口味蛋糕的部分分发给会员)。

  • 分配后会浪费一些体积的蛋糕,我们希望尽量减少浪费;或者,等效地,我们追求最大分配政策

给定已知条件:如果 V 是最优解,则至少一个蛋糕 X 可以除以 V 而没有任何剩余体积,即 Vx mod V == 0

我正在尝试寻找具有最佳时间复杂度的解决方案(蛮力会做到这一点,但我需要一种更快的方法)。

任何建议将不胜感激。

谢谢

PS:不是作业,是面试题。这是蛮力的pseducode:

int return_Max_volumn(List VolumnList)
{
    maxVolumn = 0;
    minimaxLeft = Integer.Max_value;
    for (Volumn v: VolumnList)
         for i = 1 to K people
             targeVolumn = v/i;
             NumberofpeoplecanGetcake = v1/targetVolumn + 
                 v2/targetVolumn + ... + vn/targetVolumn
             if (numberofPeopleCanGetcake >= k)
                  remainVolumn = (v1 mod targetVolumn) + (v2 mod targetVolumn)
                   + (v3 mod targetVolumn + ... + (vn mod targetVolumn)
                  if (remainVolumn < minimaxLeft)
                       update maxVolumn to be targetVolumn;
                       update minimaxLeft to be remainVolumn


     return maxVolumn
}
4

4 回答 4

9

This is a somewhat classic programming-contest problem.

The answer is simple: do a basic binary search on volume V (the final answer).

(Note the title says M people, yet the problem description says K. I'll be using M)

Given a volume V during the search, you iterate through all of the cakes, calculating how many people each cake can "feed" with single-flavor slices (fed += floor(Vi/V)). If you reach M (or 'K') people "fed" before you're out of cakes, this means you can obviously also feed M people with any volume < V with whole single-flavor slices, by simply consuming the same amount of (smaller) slices from each cake. If you run out of cakes before reaching M slices, it means you cannot feed the people with any volume > V either, as that would consume even more cake than what you've already failed with. This satisfies the condition for a binary search, which will lead you to the highest volume V of single-flavor slices that can be given to M people.

The complexity is O(n * log((sum(Vi)/m)/eps) ). Breakdown: the binary search takes log((sum(Vi)/m)/eps) iterations, considering the upper bound of sum(Vi)/m cake for each person (when all the cakes get consumed perfectly). At each iteration, you have to pass through at most all N cakes. eps is the precision of your search and should be set low enough, no higher than the minimum non-zero difference between the volume of two cakes, divided by M*2, so as to guarantee a correct answer. Usually you can just set it to an absolute precision such as 1e-6 or 1e-9.

To speed things up for the average case, you should sort the cakes in decreasing order, such that when you are trying a large volume, you instantly discard all the trailing cakes with total volume < V (e.g. you have one cake of volume 10^6 followed by a bunch of cakes of volume 1.0. If you're testing a slice volume of 2.0, as soon as you reach the first cake of volume 1.0 you can already return that this run failed to provide M slices)

Edit:

The search is actually done with floating point numbers, e.g.:

double mid, lo = 0, hi = sum(Vi)/people;
while(hi - lo > eps){
    mid = (lo+hi)/2;
    if(works(mid)) lo = mid;
    else hi = mid;
}
final_V = lo;

By the end, if you really need more precision than your chosen eps, you can simply take an extra O(n) step:

// (this step is exclusively to retrieve an exact answer from the final
// answer above, if a precision of 'eps' is not acceptable)
foreach (cake_volume vi){
   int slices = round(vi/final_V);
   double difference = abs(vi-(final_V*slices));
   if(difference < best){
       best = difference;
       volume = vi;
       denominator = slices;
   }
}
// exact answer is volume/denominator
于 2013-06-07T15:48:23.410 回答
1

这是我会考虑的方法:

假设我们所有的蛋糕都是按照非递减大小的顺序排序的,这意味着Vn最大的蛋糕和V1最小的蛋糕。

  1. 通过仅在所有人之间划分最大的蛋糕来生成第一个中间解决方案k。即V = Vn / k
  2. 立即丢弃所有小于的蛋糕V- 任何涉及这些蛋糕的中间解决方案都保证比我们在步骤 1 中的中间解决方案更差。现在我们剩下蛋糕Vb, ..., Vn,其中b大于或等于 1。
  3. 如果除了最大的蛋糕之外所有的蛋糕都被丢弃了,那么我们就完成了。V是解决方案。结尾。
  4. 由于我们还剩下一个以上的蛋糕,让我们通过将一些切片重新分配到第二大蛋糕来改进我们的中间解决方案Vn-1,即找到最大的值Vso that floor(Vn / V) + floor(Vn-1 / V) = k。这可以通过在 的当前值V和上限之间执行二进制搜索来完成(Vn + Vn-1) / k,或者通过更聪明的方法来完成。
  5. 再次,就像我们在步骤 2 中所做的那样,立即丢弃所有小于 的蛋糕V- 任何涉及这些蛋糕的中间解决方案都保证比我们在步骤 4 中的中间解决方案更差。
  6. 如果除了两个最大的蛋糕之外所有的蛋糕都被丢弃了,那么我们就完成了。V是解决方案。结尾。
  7. 继续从右到左的新“大”饼,改进中间解,从左到右继续丢弃“小”饼,直到所有剩余的饼用完。

PS第4步的复杂度似乎相当于整个问题的复杂度,也就是说上面可以看作是一种优化方法,但不是真正的解决方案。哦,好吧,为了它的价值...... :)

于 2013-06-07T19:13:56.497 回答
0

这是一种更有效的解决方案。您的蛮力解决方案本质上会生成一个隐含的可能卷,按可行性过滤它们,并返回最大的。我们可以稍微修改它以实现列表并对其进行排序,以便找到的第一个可行解决方案是最大的。

您的第一个任务:找到一种按需生成排序列表的方法。换句话说,我们应该做 O(n + m log n) 工作来生成前 m 个项目。

现在,让我们假设出现在列表中的卷是成对不同的。(我们可以稍后取消这个假设。)有一个有趣的事实是位置 k 的音量服务了多少人。例如,卷 11、13、17 和 7 人,列表为 17、13、11、17/2、13/2、17/3、11/2、13/3、17/4、11/3 , 17/5, 13/4, 17/6, 11/4, 13/5, 17/7, 11/5, 13/6, 13/7, 11/6, 11/7。

您的第二个任务:模拟此列表中的蛮力算法。利用你注意到的东西。

于 2013-06-07T15:26:44.247 回答
-2

所以这是我认为可行的算法:

  1. 将卷从最大到最小排序。
  2. 将最大的蛋糕分给1...k人,即target = volume[0]/i,其中i = 1,2,3,4,...,k
  3. 如果目标会导致总件数大于 k,则减少数量 i 并重试。
  4. 找到第一个数字i,它将导致总块数大于或等于 K,但(i-1)将导致蛋糕总数小于 k。将此卷记录为 baseVolume。
  5. 对于每个剩余的蛋糕,找到剩余体积除以人数的最小分数,即除法 = (V_cake - (baseVolume*(Math.floor(V_cake/baseVolume))) / Math.floor(V_cake/baseVolume)
  6. 将此数量添加到 baseVolume(baseVolume += division) 并重新计算所有卷可以划分的总块数。如果新卷导致的碎片较少,则返回先前的值,否则,重复步骤 6。

以下是java代码:

public static int getKonLagestCake(Integer[] sortedVolumesList, int k) {
    int result = 0;
    for (int i = k; i >= 1; i--) {
        double volumeDividedByLargestCake = (double) sortedVolumesList[0]
                / i;
        int totalNumber = totalNumberofCakeWithGivenVolumn(
                sortedVolumesList, volumeDividedByLargestCake);
        if (totalNumber < k) {
                result = i + 1;
                break;
        }
    }
    return result;
}


public static int totalNumberofCakeWithGivenVolumn(
        Integer[] sortedVolumnsList, double givenVolumn) {
    int totalNumber = 0;
    for (int volume : sortedVolumnsList) {
        totalNumber += (int) (volume / givenVolumn);
    }
    return totalNumber;
}

public static double getMaxVolume(int[] volumesList, int k) {
    List<Integer> list = new ArrayList<Integer>();
    for (int i : volumesList) {
        list.add(i);
    }

    Collections.sort(list, Collections.reverseOrder());
    Integer[] sortedVolumesList = new Integer[list.size()];
    list.toArray(sortedVolumesList);

    int previousValidK = getKonLagestCake(sortedVolumesList, k);
    double baseVolume = (double) sortedVolumesList[0] / (double) previousValidK;

    int totalNumberofCakeAvailable = totalNumberofCakeWithGivenVolumn(sortedVolumesList, baseVolume);

    if (totalNumberofCakeAvailable == k) {
        return baseVolume;
    } else {
        do
        {
            double minimumAmountAdded = minimumAmountAdded(sortedVolumesList, baseVolume);

            if(minimumAmountAdded == 0)
            {
                return baseVolume;
            }else
            {
                baseVolume += minimumAmountAdded;
                int newTotalNumber = totalNumberofCakeWithGivenVolumn(sortedVolumesList, baseVolume);

                if(newTotalNumber == k)
                {
                    return baseVolume;
                }else if (newTotalNumber < k)
                {
                    return (baseVolume - minimumAmountAdded);
                }else
                {
                    continue;
                }
            }

        }while(true);
    }

}

public static double minimumAmountAdded(Integer[] sortedVolumesList, double volume)
{
    double mimumAdded = Double.MAX_VALUE;
    for(Integer i:sortedVolumesList)
    {
        int assignedPeople = (int)(i/volume);
        if (assignedPeople == 0)
        {
            continue;
        }
        double leftPiece = (double)i - assignedPeople*volume;

        if(leftPiece == 0)
        {
            continue;
        }

        double division = leftPiece / (double)assignedPeople;
        if (division < mimumAdded)
        {
            mimumAdded = division;
        }
    }

    if (mimumAdded == Double.MAX_VALUE)
    {
        return 0;
    }else
    {
        return mimumAdded;
    }
}

任何意见将不胜感激。谢谢

于 2013-06-08T15:54:57.843 回答