【问题标题】:Dividing K resources fairly to N people将 K 资源公平分配给 N 个人
【发布时间】:2018-09-05 10:35:23
【问题描述】:

圆圈上有K 点,代表宝藏的位置。 N人们想分享宝藏。您希望将宝物公平地分配给所有人,以使具有最大值的人和具有最小值的人之间的差异尽可能小。

  • 它们都采用圆上的一组连续点。也就是说,他们 不能拥有分段的宝藏。
  • 宝物必须全部分配
  • 每件宝物只属于一个人。

比如图中有4个宝物和2个人,那么最优的划分方式是

(6, 10) 和 (11, 3) => 相差 2。

1

1

我该如何解决这个问题?我计划计算所有点的平均值并继续添加资源,直到它们小于每个人的平均值。但很明显,它并非在所有情况下都有效。

如果有人能点亮我会很高兴。

【问题讨论】:

标签: algorithm math discrete-mathematics


【解决方案1】:

假设我们将 x, y 固定为宝藏允许的最小最大值。 我需要弄清楚我们是否可以在这些限制中找到解决方案。

为此,我需要遍历圆并精确地创建 N 段,总和在 x 和 y 之间。 如果我可以将 i 和 j 之间的元素拆分为总和在 x 和 y 之间的 l (见上文),我可以通过动态编程解决这个问题,a[i][j][l] = 1。为了计算它,我们可以计算 a[i][j][l] = is_there_a_q_such_that(a[i][q - 1][l-1] == 1 and sum(q -> j) between x and y)。 要处理圆,请查找覆盖足够元素的 n-1 个线段,剩余的差异仍然在 x 和 y 之间。

所以天真的解决方案是 O(total_sum^2) 选择 X 和 Y 加上 O(K^3) 迭代 i,j,l 和另一个 O(K) 来找到一个 q 和另一个 O(K) 得到总和。总共 O(total_sum^2 * K^5) 可能太慢了。

所以我们需要大量计算总和。所以让我们预先计算一个部分和数组 sums[w] = sum(pos 0 和 pos w 之间的元素)。所以要得到 q 和 j 之间的和,你只需要计算 sums[j] - sums[q-1]。这需要 O(K)。

计算 a[i][j][l]。 由于宝物总是正的,如果部分和太小,我们需要扩大区间,如果和太高,我们需要缩小区间。由于我们固定了区间的一侧(在 j 处),我们只能移动 q。我们可以使用二分搜索来找到允许我们位于 x 和 y 之间的最接近的 t 和最远的 q。我们称它们为 low_q(最接近 j,最小和)和 high_q(远离 j,最大和)。如果 low_q

前面还有total_sum^2。假设我们修复了 X。如果对于给定的 y,我们有一个解决方案,您可能还可以找到一个仍然有解决方案的较小的 y。如果您找不到给定 y 的解决方案,那么您将无法找到任何较小值的解决方案。所以我们现在可以对 y 进行二分搜索。

所以现在是 O(total_sum *log(total_sum) * K^3 * logK)。

如果 sum(0-> i- 1) > x,其他优化是不提高 i。 您可能不想检查 x > total_sum/K 的值,因为这是理想的最小值。这应该抵消了其中一个 K 是复杂性。

您可能还可以做其他事情,但我认为这对于您的限制来说已经足够快了。

【讨论】:

  • 所以如果总和是 10000 并且 K 是 50,我们正在查看 10000 * log2 10000 * 50^2 * log2 50 = 1,874,848,444 次迭代?我理解正确吗?
【解决方案2】:

您可以对 O(k^n) 进行暴力破解,也可以对 O(k^{2}*MAXSUM^{k — 1}) 进行 dp。

dp[i][val1][val2]...[val k -1] 是否可以分配前 k 个项目,所以首先有 val1,第二个 - val2 等等。有 k * MAXSUM(k - 1) 个状态,你需要 O(k) 来做一步,你只需选择谁接受第 i 个项目。

我认为不可能更快地解决它。

【讨论】:

    【解决方案3】:

    对于这个问题没有标准类型的算法(贪婪、分而治之等)。
    您必须检查(resource, people) 的每一个组合并选择答案。使用递归解决问题后,您可以抛出DP 来优化解决方案。

    解的曲线是:

    Recuse through all the treasures
        If you current treasure is not the last,
            set minimum difference to Infinity
                for each user
                    assign the current treasure to the current user 
                    ans = recurse further by going to the next treasure
                    update minimumDifference if necessary
        else 
           Find and max amount of treasure assigned and minimum amount of treasure assigned
           and return the difference
    


    这是答案的javascript版本。 我也评论了它以尝试解释逻辑:

    // value of the treasure
    const K = [6, 3, 11, 10];
    // number of users
    const N = 2;
    
    // Array which track amount of treasure with each user
    const U = new Array(N).fill(0);
    
    // 2D array to save whole solution
    const bitset = [...new Array(N)].map(() => [...new Array(K.length)]);
    
    const solve = index => {
        /**
         * The base case:
         * We are out of treasure.
         * So far, the assigned treasures will be in U array
         */
        if (index >= K.length) {
            /**
             * Take the maximum and minimum and return the difference along with the bitset
             */
            const max = Math.max(...U);
            const min = Math.min(...U);
            const answer = { min: max - min, set: bitset };
            return answer;
        }
    
        /**
         * We have treasures to check
         */
        let answer = { min: Infinity, set: undefined };
        for (let i = 0; i < N; i++) {
            // Let ith user take the treasure
            U[i] += K[index];
            bitset[i][index] = 1;
            /**
             * Let us recuse and see what will be the answer if ith user has treasure at `index`
             * Note that ith user might also have other treasures for indices > `index`
             */
            const nextAnswer = solve(index + 1);
    
            /**
             * Did we do better?
             * Was the difference bw the distribution of treasure reduced?
             * If so, let us update the current answer
             * If not, we can assign treasure at `index` to next user (i + 1) and see if we did any better or not
             */
            if (nextAnswer.min <= answer.min) {
                answer = JSON.parse(JSON.stringify(nextAnswer));
            }
    
            /**
             * Had we done any better,the changes might already be recorded in the answer.
             * Because we are going to try and assign this treasure to the next user,
             * Let us remove it from the current user before iterating further
             */
            U[i] -= K[index];
            bitset[i][index] = 0;
        }
        return answer;
    };
    
    const ans = solve(0);
    console.log("Difference: ", ans.min);
    console.log("Treasure: [", K.join(", "), "]");
    console.log();
    ans.set.forEach((x, i) => console.log("User: ", i + 1, " [", x.join(", "), "]"));

    索引i 处的每个问题都会精确地创建N 的副本 本身和我们总共有K 索引,问题的时间复杂度 要解决的是O(K^N)


    我们绝对可以通过抛出 memoization 做得更好。

    棘手的部分来了:

    如果我们为一位用户分配宝藏,最低 用户之间的财富分配差异将是 一样的。

    在这种情况下,bitset[i] 代表ith 用户的分布。 因此,我们可以记住用户bitset的结果。

    你意识到,编码很容易:

    // value of the treasure
    const K = [6, 3, 11, 10, 1];
    // number of users
    const N = 2;
    
    // Array which track amount of treasure with each user
    const U = new Array(N).fill(0);
    
    // 2D array to save whole solution
    const bitset = [...new Array(N)].map(() => [...new Array(K.length).fill(0)]);
    
    const cache = {};
    
    const solve = (index, userIndex) => {
        /**
         * Do we have cached answer?
         */
        if (cache[bitset[userIndex]]) {
            return cache[bitset[userIndex]];
        }
    
        /**
         * The base case:
         * We are out of treasure.
         * So far, the assigned treasures will be in U array
         */
        if (index >= K.length) {
            /**
             * Take the maximum and minimum and return the difference along with the bitset
             */
            const max = Math.max(...U);
            const min = Math.min(...U);
            const answer = { min: max - min, set: bitset };
            // cache the answer
            cache[bitset[userIndex]] = answer;
            return answer;
        }
    
        /**
         * We have treasures to check
         */
        let answer = { min: Infinity, set: undefined };
        // Help us track the index of the user with optimal answer
        let minIndex = undefined;
        for (let i = 0; i < N; i++) {
            // Let ith user take the treasure
            U[i] += K[index];
            bitset[i][index] = 1;
            /**
             * Let us recuse and see what will be the answer if ith user has treasure at `index`
             * Note that ith user might also have other treasures for indices > `index`
             */
            const nextAnswer = solve(index + 1, i);
    
            /**
             * Did we do better?
             * Was the difference bw the distribution of treasure reduced?
             * If so, let us update the current answer
             * If not, we can assign treasure at `index` to next user (i + 1) and see if we did any better or not
             */
            if (nextAnswer.min <= answer.min) {
                answer = JSON.parse(JSON.stringify(nextAnswer));
                minIndex = i;
            }
    
            /**
             * Had we done any better,the changes might already be recorded in the answer.
             * Because we are going to try and assign this treasure to the next user,
             * Let us remove it from the current user before iterating further
             */
            U[i] -= K[index];
            bitset[i][index] = 0;
        }
        cache[answer.set[minIndex]] = answer;
        return answer;
    };
    
    const ans = solve(0);
    
    console.log("Difference: ", ans.min);
    console.log("Treasure: [", K.join(", "), "]");
    console.log();
    ans.set.forEach((x, i) => console.log("User: ", i + 1, " [", x.join(", "), "]"));
    
    // console.log("Cache:\n", cache);

    我们绝对可以通过不缓存整个位集来改善使用的空间。从 cahce 中删除 bitset 很简单。

    【讨论】:

    • 这里“连续”的意思是,例如,如果 4-5-6-7 是圆上的点,则任何用户都不能采用分段集,例如 4,6 或 5,7。每个用户必须取一个连续的集合,例如 4,5 或 5,6,7 等。
    • 现在我阅读了您的解释,您的观点似乎也有效。让 OP 为我清除它,我会更新答案。感谢您指出:)
    • @גלעדברקן,是的,你是对的。您只能拿走一组连续的宝藏。
    【解决方案4】:

    考虑对于每个k,我们可以将一个从A[i] 向左(sum A[i-j..i])增长的总和与为f(k-1, i-j-1) 记录的所有可用间隔配对并更新它们:对于每个间隔,(low, high),如果总和大于high,则new_interval = (low, sum),如果总和小于low,则new_interval = (sum, high);否则,间隔保持不变。例如,

    i:  0 1 2 3 4 5
    A: [5 1 1 1 3 2]
    
    k = 3
    i = 3, j = 0
    The ordered intervals available for f(3-1, 3-0-1) = f(2,2) are:
      (2,5), (1,6) // These were the sums, (A[1..2], A[0]) and (A[2], A[0..1])
    Sum = A[3..3-0] = 1
    Update intervals: (2,5) -> (1,5)
                      (1,6) -> (1,6) no change
    

    现在,我们可以通过在上一轮k 中识别和修剪间隔来提高此迭代的效率很多

    观看:

    A: [5 1 1 1 3 2]
    

    K = 1:

      N = 0..5; Intervals: (5,5), (6,6), (7,7), (8,8), (11,11), (13,13)
    

    K = 2:

      N = 0: Intervals: N/A
    
      N = 1: Intervals: (1,5)
    
      N = 2: (1,6), (2,5)
    
        Prune: remove (1,6) since any sum <= 1 would be better paired with (2,5)
               and any sum >= 6 would be better paired with (2,5)
    
      N = 3: (1,7), (2,6), (3,5)
    
        Prune: remove (2,6) and (1,7)
    
      N = 4: (3,8), (4,7), (5,6), (5,6)
    
        Prune: remove (3,8) and (4,7)
    
      N = 5: (2,11), (5,8), (6,7)
    
        Prune: remove (2,11) and (5,8)
    

    对于k = 2,我们现在留下以下修剪后的记录:

    {
      k: 2,
      n: {
        1: (1,5),
        2: (2,5),
        3: (3,5),
        4: (5,6),
        5: (6,7)
      }
    }
    

    我们已将k = 3 的迭代从n choose 2 可能的拆分列表缩减为n 相关拆分!

    应用于k = 3的通用算法:

    for k' = 1 to k
      for sum A[i-j..i], for i <- [k'-1..n], j <- [0..i-k'+1]:
        for interval in record[k'-1][i-j-1]: // records are for [k'][n']
          update interval
      prune intervals in k'
    
    k' = 3
      i = 2
        sum = 1, record[2][1] = (1,5) -> no change
    
      i = 3
        // sums are accumulating right to left starting from A[i]
        sum = 1, record[2][2] = (2,5) -> (1,5)
        sum = 2, record[2][1] = (1,5) -> no change
    
      i = 4
        sum = 3, record[2][3] = (3,5) -> no change
        sum = 4, record[2][2] = (2,5) -> no change
        sum = 5, record[2][1] = (1,5) -> no change
    
      i = 5
        sum = 2, record[2][4] = (5,6) -> (2,6)
        sum = 5, record[2][3] = (3,5) -> no change
        sum = 6, record[2][2] = (2,5) -> (2,6)
        sum = 7, record[2][1] = (1,5) -> (1,7)
    

    答案是5record[2][3] = (3,5) 配对,产生更新的间隔(3,5)。我将把修剪逻辑留给读者自己解决。如果我们想继续,这里是 k = 3 的删减列表

    {
      k: 3
      n: {
        2: (1,5), 
        3: (1,5),
        4: (3,5),
        5: (3,5)
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多