将N饼分成最少浪费的M人

时间:2013-06-07 14:43:27

标签: algorithm data-structures

所以这就是问题:

在派对上有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 个答案:

答案 0 :(得分:9)

This is a somewhat classic programming-contest problem

答案很简单:在卷V上进行基本二进制搜索(最终答案)。

(注意标题是M人,但问题描述说K.我将使用M

在搜索过程中给定一个量V,您将遍历所有蛋糕,计算每个蛋糕可以用单味切片(fed += floor(Vi/V))“喂”多少人。如果你在失去蛋糕之前到达M(或'K')的人“喂”,这意味着你显然也可以向M人提供任何音量&lt;通过简单地从每个蛋糕中消耗相同量的(较小的)切片,V具有完整的单味切片。如果在到达M切片之前蛋糕用完,则意味着您无法为任何体积的人提供食物&gt; V要么,因为这会消耗比你已经失败的蛋糕更多的蛋糕。这满足了二元搜索的条件,它将引导您获得可以提供给M个人的最高音量 V 的单味片。

复杂性为O(n * log((sum(Vi)/m)/eps) )。细分:二元搜索采用log((sum(Vi)/ m)/ eps)迭代,考虑每个人sum(Vi)/m蛋糕的上限(当所有蛋糕都被完美消耗时)。在每次迭代中,您必须通过最多所有N个蛋糕。 eps是您搜索的精确度,应设置得足够低,不高于两个蛋糕体积之间的最小非零差异除以M*2,以保证正确答案。通常,您只需将其设置为绝对精度,例如1e-61e-9

为了加快普通情况的速度,您应该按顺序对蛋糕进行分类,这样当您尝试大量时,您会立即丢弃总体积<1的所有尾随蛋糕。 V(例如,您有一个卷10^6的蛋糕,后面是一堆卷1.0的蛋糕。如果您正在测试切片量为2.0,请尽快您到达了第一个卷1.0的蛋糕,您已经可以返回该运行未能提供M切片)

编辑:

搜索实际上是使用浮点数来完成的,例如:

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;

最后,如果你真的需要比你选择的eps 更精确的话,你可以简单地采取额外的O(n)步骤:

// (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

答案 1 :(得分:1)

以下是我会考虑的方法:

让我们假设我们所有的蛋糕都按照不减小的大小排序,这意味着Vn是最大的蛋糕,而V1是最小的蛋糕。

  1. 通过仅划分所有k人之间最大的蛋糕来生成第一个中间解决方案。即V = Vn / k
  2. 立即丢弃所有小于V的蛋糕 - 任何涉及这些蛋糕的中间溶液都会比我们在步骤1中的中间溶液更差。现在我们留下了蛋糕Vb, ..., Vn,其中b大于或等于1.
  3. 如果除了最大的蛋糕之外所有蛋糕都被丢弃了,那么我们就完成了。 V是解决方案。 END。
  4. 由于我们剩下一个以上的蛋糕,让我们通过将一些切片重新分配到第二大蛋糕Vn-1来改进我们的中间解决方案,即找到V的最大值,以便{{1} }}。这可以通过在floor(Vn / V) + floor(Vn-1 / V) = k的当前值和上限V之间执行二进制搜索来完成,或者通过更聪明的事情来完成。
  5. 再次,就像我们在第2步中所做的那样,立即丢弃所有小于(Vn + Vn-1) / k的蛋糕 - 任何涉及这些蛋糕的中间溶液都会比我们的中间解决方案更糟糕从第4步开始。
  6. 如果除了两个最大的蛋糕之外所有的蛋糕都被丢弃了,那么我们就完成了。 V是解决方案。 END。
  7. 继续从左到右的方向介入新的“大”蛋糕,改进中间解决方案,并继续从左到右的方向丢弃“小”蛋糕,直到所有剩余的蛋糕用完为止。
  8. P.S。步骤4的复杂性似乎等同于整个问题的复杂性,这意味着上述内容可以被视为一种优化方法,但不是真正的解决方案。噢,为了它的价值......:)

答案 2 :(得分: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。

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

答案 3 :(得分:-2)

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

  1. 将卷从最大到最小排序。
  2. 将最大的蛋糕分为1 ... k个人,即目标=体积[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.
  7. 以下是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;
        }
    }
    

    任何评论将不胜感激。 感谢