簡體   English   中英

在容量為M的K個房間中分配N噸食物

[英]Allocating N tonnes of food in K rooms with M capacity

我在網上發現了這個問題:

您有N噸的食物和K個房間來存放它們。 每個房間的容量為M。您可以通過多種方式分配房間中的食物,因此每個房間至少要有1噸食物。

我的方法是遞歸地找到滿足問題條件的所有可能變化。 我從大小為K的數組開始,初始化為1。然后繼續向數組的每個元素加1,然后遞歸檢查新數組是否滿足條件。 但是,遞歸樹變得太大而又過快,並且對於N,K和M稍高的值,程序花費的時間太長。

什么是實現此任務的更有效算法? 是否需要對現有算法實現進行優化?

這是我的實現:

import java.util.Arrays;
import java.util.HashSet;
import java.util.Scanner;

public class Main {
    // keeping track of valid variations, disregarding duplicates
    public static HashSet<String> solutions = new HashSet<>();

    // calculating sum of each variation
    public static int sum(int[] array) {
        int sum = 0;
        for (int i : array) {
            sum += i;
        }

        return sum;
    }

    public static void distributionsRecursive(int food, int rooms, int roomCapacity, int[] variation, int sum) {
        // if all food has been allocated
        if (sum == food) {
            // add solution to solutions
            solutions.add(Arrays.toString(variation));
            return;
        }

        // keep adding 1 to every index in current variation
        for (int i = 0; i < rooms; i++) {
            // create new array for every recursive call
            int[] tempVariation = Arrays.copyOf(variation, variation.length);
            // if element is equal to room capacity, can't add any more in it
            if (tempVariation[i] == roomCapacity) {
                continue;
            } else {
                tempVariation[i]++;
                sum = sum(tempVariation);
                // recursively call function on new variation
                distributionsRecursive(food, rooms, roomCapacity, tempVariation, sum);
            }
        }
        return;
    }

    public static int possibleDistributions(int food, int rooms, int roomCapacity) {
        int[] variation = new int[rooms];
        // start from all 1, keep going till all food is allocated
        Arrays.fill(variation, 1);
        distributionsRecursive(food, rooms, roomCapacity, variation, rooms);
        return solutions.size();
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int food = in.nextInt();
        int rooms = in.nextInt();
        int roomCapacity = in.nextInt();

        int total = possibleDistributions(food, rooms, roomCapacity);
        System.out.println(total);
        in.close();
    }
}

是的,如果您天真地執行此操作,則遞歸樹將變大。 假設您有10噸和3個房間,M = 2。 一種有效的安排是[2,3,5]。 但是您也有[2,5,3],[3,2,5],[3,5,2],[5,2,3]和[5,3,2]。 因此,對於每個有效的數字分組,實際上都有K! 排列。

解決此問題的一種可能更好的方法是確定可以使K個數字(最小M和最大N)加起來為N的方法。首先,使第一個數字盡可能大,即N-(M*(K-1)) 在我的示例中,將是:

10 - 2*(3-1) = 6

給出答案[6,2,2]。

然后,您可以構建算法,通過從左到右“移動”值來調整數字以得出有效的組合。 在我的示例中,您將擁有:

6,2,2
5,3,2
4,4,2
4,3,3

通過確保值從左到右遞減,可以避免看似無限的遞歸。 例如,在上面,您將永遠不會有[3,4,3]。

如果您確實想要所有有效的安排,則可以為上述每種組合生成排列。 我懷疑這不是必需的。

我認為這足以使您開始尋求一個好的解決方案。

一種解決方案是從k-1個房間的結果計算k個房間的結果。

我已經簡化了這個問題,允許在一個房間中存儲0噸。 如果我們必須存儲至少1個,則可以預先減去此值,並將房間容量減少1個。

因此,我們定義一個函數calc:(Int,Int)=> List [Int],該函數計算多個房間和一個容量的組合數量列表。 第一個條目包含用於存儲0的組合數,第二個條目存儲1時的組合數,依此類推。

我們可以輕松地為一個房間計算此函數。 因此calc(1,m)給出了一個由第m個元素組成的列表,然后僅包含零。

對於較大的k,我們可以遞歸定義此函數。 我們只計算calc(k-1,m),然后通過匯總舊列表的前綴來構建新列表。 例如,如果我們要存儲5噸,我們可以將所有5個存儲在第一個房間中,將0存儲在隨后的房間中,或將4個存儲在第一個房間中,然后將1個存儲在接下來的房間中,依此類推。 因此,我們必須將其余房間的組合匯總為0到5。

由於我們擁有最大的容量,因此我們可能不得不省略一些組合,即,如果房間僅具有3個容量,我們就不能計算剩余的組合中用於存儲0噸和1噸的組合。

我已經在Scala中實現了這種方法。 我使用了流(即無限列表),但是您知道您需要的最大元素數量是不必要的。

該方法的時間復雜度應為O(k * n ^ 2)

def calc(rooms: Int, capacity: Int): Stream[Long] =
  if(rooms == 1) {
    Stream.from(0).map(x => if(x <= capacity) 1L else 0L)
  } else {
    val rest = calc(rooms - 1, capacity)
    Stream.from(0).map(x => rest.take(x+1).drop(Math.max(0,x - capacity)).sum)
  }

您可以在這里嘗試:

http://goo.gl/tVgflI

(我已經用BigInt替換了Long以使其適用於更大的數字)

第一個技巧是刪除distributionsRecursive ,不要建立解決方案列表。 所有解決方案的列表都是一個巨大的數據集。 只要產生一個計數。

這將使您可以將possibleDistributions轉換為根據自身定義的遞歸函數。 遞歸步驟將是, possibleDistributions(food, rooms, roomCapacity) = sum from i = 1 to roomCapacity of possibleDistributions(food - i, rooms - 1, roomCapacity)

您將節省大量內存,但是仍然存在潛在的性能問題。 但是,通過純遞歸功能,您現在可以使用https://en.wikipedia.org/wiki/Memoization修復該問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM