解决这个分配珠子难题的算法?

2024-01-20

假设你有一个圆圈(如下所示)N点,并且你有N珠子分布在槽中。

Here's an example: enter image description here

每个珠子都可以顺时针移动X插槽,这需要花费X^2美元。您的目标是最终在每个槽中获得一颗珠子。完成这项任务至少需要花多少钱?

这个问题更有趣的变体:分配珠子难题的算法(2)? https://stackoverflow.com/questions/35539630/algorithm-for-distributing-beads-puzzle-2


在这个答案中,我假设珠子只能移动一次。否则,显然珠子一次只能移动一格,这使得这个问题变得不那么有趣:平方和将降级为简单的移动总和。

问题可以解决O(n)时间,在第 4 点我给出了一个 JavaScript 实现。如果您想跳过导致该算法的推理,只需滚动到该部分即可。

Edit:我添加了一个部分B,其中分析了问题的变体。

但以下是针对当前问题提出建议算法的观察结果:

1. 禁止超车

应该观察到,在最佳解决方案中,珠子永远不会移动以致一个珠子“超过”另一个珠子。这两个珠子的替代解决方案 交换他们的目标槽位总是会得到较低的平方和。

为了形式化一点,假设一颗珠子从槽号移动a to b,另一个来自c to d。要求它们不能逆时针移动。现在让我们假设第一个超越另一个,那么我们就有了所有这些事实:

1. a < b
2. c < d
3. a < c
4. b > d

然后超越版本的移动平方和替代版本(珠子交换目标)的移动平方,如下比较:

(b-a)² + (d-c)² > (d-a)² + (b-c)²

证明上述不等式可分解为:

b²-2ab+a² + d²-2cd+c² > d²-2ad+a² + b²-2bc+c²
-2ab + -2cd > -2ad + -2bc
ab + cd < ad + bc
a(b-d) < c(b-d)
a < c
true

因此,在开始时共享同一槽的珠子将始终位于最佳解决方案中的相邻槽中。

更普遍:

2. 仅查看珠子保持相同顺序的解决方案

因此,如果我们给在同一槽中开始的珠子一个(任意)顺序,我们可以说可以找到一个最佳解决方案,其中珠子的顺序与原始输入中的顺序相同(因为排除了超车) )。

这也意味着我们可以很容易地找到一个候选解决方案,我们可以按订单号拾取珠子并将它们放入具有相同订单号的插槽中。这可能不是最佳解决方案,但有可能是。如果它包含逆时针移动,它甚至可能无效。但作为开始还是很有用的。

3. 循环解至最优解

通过将所有珠子移动到下一个槽,可以找到保持相同顺序的任何其他潜在解决方案,直到找到有效且最佳的解决方案。我将这样的举动称为循环。

如果第一个候选解因逆时针移动而无效,则可以直接找到最佳解决方案:采取最大的逆时针移动,并将该移动的相反移动添加到所有移动(包括该移动)中。这将使所有有问题的移动都变成非逆时针方向(至少有一个移动为零)。骑得越远显然会使平方和变大。所以这是最优解。

另一方面,如果候选解决方案有效,但没有一个珠子保持在适当的位置,则可以通过相反的方式循环来改进解决方案,即通过使移动更小,直到至少一个珠子保持在适当的位置。

4. 算法

有了上述所有信息,我在这里展示了用 JavaScript 实现的算法,可以在这个实时片段中进行测试:

function optimalSpread(beadCounts) {
    // Initialisation
    var n = beadCounts.length;
    var solution = {
        // Keep track of sum of squares of moves
        // A move is a number of slots and only be positive (clockwise).
        sumSquares: 0,
        // Keep track of sum of moves.
        sumMoves: 0,
        // Per slot, keep an array with one element per bead, but
        // we start with empty arrays
        movesPerSlotPerBead: [],
    };
    // Build a first a non-optimised solution. 
    // Assign beads in FIFO order to consecutive slots.
    // *move*, when added to the current slot number, identifies the first slot where
    // a bead needs to end up in this solution. Note that this initial solution
    // may do counter-clockwise moves. This will be corrected later.
    // =O(n)
    var move = 0,
        totalBeadCount = 0,
        minMove = 0;
    beadCounts.forEach(function(beadCount, slotId) {
        // check sum
        totalBeadCount += beadCount;
        // See where the next bead would be moved (relative to slot)
        move += beadCount - 1;
        // and keep the minimum value. Can be negative, meaning a 
        // counter clockwise move.
        if (move < minMove) minMove = move;
    });
    // abort if number of beads is not equal to number of slots
    if (totalBeadCount !== n) return {error: "invalid input"}; 
    // Improve solution by making sure there are no counter-clockwise
    // moves, and at least one bead stays unmoved (0).
    // Apply correction we got from previous loop to ensure this.
    // =O(n)
    move = -minMove;
    beadCounts.forEach(function(beadCount, slotId) {
        solution.movesPerSlotPerBead[slotId] = [];
        // Move each bead into next available slot
        for (; beadCount > 0; beadCount--, move++) {
            // Store the move for this bead
            solution.movesPerSlotPerBead[slotId].push(move);
            solution.sumMoves += move;
            solution.sumSquares += move*move;
        }
        // Compensate the increment of slotId
        move--;
    });
    // The solution is now optimal:
    // Cycling counter-clockwise would make at least one bead go that way;
    // Cycling clockwise would increase the sum of squares (as all moves are
    //    non-negative values)
    return solution;
}

function randomInput(n) {
    // Start with slots having all zero beads:
    beadCounts = Array.from({length: n}, x => 0);
    // Randomly assign beads to slots, keeping a count per slot
    for (var i = 0; i < n; i++) {
        beadCounts[Math.floor(Math.random() * n)]++;
    }
    return beadCounts;
}

// Link with I/O
var input = document.getElementById('input');
var randomize = document.getElementById('randomize');
var calculate = document.getElementById('calculate');
var output = document.getElementById('output');

// Capture events
randomize.onclick = function() {
    var n = 5 + Math.floor(Math.random() * 20);
    input.value = randomInput(n).join(',');
    calculate.onclick();
};

calculate.onclick = function() {
    var beadCounts = input.value.split(',').map(Number);
    var solution = optimalSpread(beadCounts);
    if (solution.error) {
        output.textContent = 'Error: ' + solution.error;
        return;
    }
    output.textContent = 
        '\nInput: ' + JSON.stringify(beadCounts) +
        '\nSum of squares of moves: ' + solution.sumSquares +
        '\nSum of moves: ' + solution.sumMoves +
        '\nMoves[slot][bead]: ' + JSON.stringify(solution.movesPerSlotPerBead);
};
Comma-separated list of number of beads per slot:<br/>
<input id="input" size="40" value="3,0,1,0,1,0,2,2,2,1,1,0,1,0">
<button id="randomize">Randomize</button><br/>
<button id="calculate">Find optimal spread</button></br>
<pre id="output"></pre>

此代码片段采用一个数组,其中每个索引代表一个槽,值代表该槽中的珠子数量。它输出原始数组、可选解决方案的平方和以及珠子的移动列表。

问题中示例问题的输出是:

Input: [3,0,1,0,1,0,2,2,2,1,1,0,1,0]
Sum of squares of moves: 60
Sum of moves: 26
Moves[slot][bead]: [[1,2,3],[],[2],[],[1],[],[0,1],[1,2],[2,3],[3],[3],[],[2],[]]

所以示例配置的答案是:60 美元。


B. 附录:无顺时针要求

如果取消了顺时针移动的要求,并且可以向任何方向移动怎么办?没有人问这个问题,但我认为这是一个有趣的变体。

以下是适用于该案例的其他观察结果:

B.1.一个解的平方和可用于下一个解

不需要实际执行循环即可找到该新配置的平方和:

假设我们有三个珠子和三个插槽,并且每个珠子都通过移动它们移动到了目标插槽x, y and z分别为插槽。例如,如果它们都在槽 0 中,我们可以得到x, y, z值 0、1、2(如果我们想夸大的话,也可以是 -1、0、1,甚至 5、6、7)。

平方和为:

x²+y²+z²

如果现在我们循环这个解决方案,从而为每个珠子的移动添加一个槽,那么方块将变为:

(x+1)²+(y+1)²+(z+1)²

or:

x²+y²+z²   +3    +2(x+y+z)

一般来说,循环具有 n 个珠子的配置会增加此项的平方和:

n + 2.sum(moves)

因此,该算法可以利用这一点并快速计算循环所得解的平方和。这可以在后续循环中重复。

B.2.平方和只有一个局部最小值

最后,每个连续循环(解)的平方和将是一个具有抛物线形状的函数,即一旦找到局部最小值,就无需寻找另一个局部最小值;没有。从上面的增加值的公式我们可以看出sum(moves)。最多两个相邻周期的平方和可能相等。

B.3.算法

下面是用 JavaScript 实现的算法:

function optimalSpread(beadCounts) {
    // Initialisation
    var n = beadCounts.length;
    var solution = {
        // Keep track of sum of squares of moves
        // A move is a number of slots and can be negative or positive.
        sumSquares: 0,
        // Keep track of sum of moves.
        sumMoves: 0,
        // Per slot, keep an array with one element per bead, but
        // we start with empty arrays
        movesPerSlotPerBead: [],
    };
    // Build a first a non-optimised solution. 
    // Assign beads in FIFO order to consecutive slots.
    // *move*, when added to the current slot number, identifies the first slot where
    // a bead needs to end up in this solution.
    // =O(n)
    var move = 0,
        totalBeadCount = 0;
    beadCounts.forEach(function(beadCount, slotId) {
        solution.movesPerSlotPerBead[slotId] = [];
        // check sum
        totalBeadCount += beadCount;
        // Move each bead into next available slot
        for (; beadCount > 0; beadCount--, move++) {
            // Store the move for this bead
            solution.movesPerSlotPerBead[slotId].push(move);
            solution.sumMoves += move;
            solution.sumSquares += move*move;
        }
        // Compensate the increment of slotId
        move--;
    });
    // abort if number of beads is not equal to number of slots
    if (totalBeadCount !== n) return {error: "invalid input"}; 
    // See if solution can be improved by shifting all beads in one direction.
    // =O(n)
    bestMoveCorrection = 0;
    while (true) {
        // Improvement is only possible in the direction dictated by sumMoves 
        moveCorrection = (solution.sumMoves < 0 ? 1 : -1);
        // Calculate the delta this brings to sumSquares:
        // (a+1)²+(b+1)²+ ... +(x+1)² = a²+b²+...+x² +n  +2(a+b+...+x) 
        sumSquaresChange = n + moveCorrection * 2 * solution.sumMoves;
        // Stop if this brings no improvement anymore
        if (sumSquaresChange >= 0) break;
        // It is an improvement; keep it
        solution.sumMoves += moveCorrection * n;
        solution.sumSquares += sumSquaresChange;
        bestMoveCorrection += moveCorrection;
    }
    // Apply correction to solution, to make it optimal
    // =O(n)
    solution.movesPerSlotPerBead.forEach(function(moves) {
        moves.forEach(function(move, idx) {
            moves[idx] += bestMoveCorrection;
        });
    });
    return solution;
}

function randomInput(n) {
    // Start with slots having all zero beads:
    beadCounts = Array.from({length: n}, x => 0);
    // Randomly assign beads to slots, keeping a count per slot
    for (var i = 0; i < n; i++) {
        beadCounts[Math.floor(Math.random() * n)]++;
    }
    return beadCounts;
}

// Link with I/O
var input = document.getElementById('input');
var randomize = document.getElementById('randomize');
var calculate = document.getElementById('calculate');
var output = document.getElementById('output');

// Capture events
randomize.onclick = function() {
    var n = 5 + Math.floor(Math.random() * 20);
    input.value = randomInput(n).join(',');
    calculate.onclick();
};

calculate.onclick = function() {
    var beadCounts = input.value.split(',').map(Number);
    var solution = optimalSpread(beadCounts);
    if (solution.error) {
        output.textContent = 'Error: ' + solution.error;
        return;
    }
    output.textContent = 
        '\nInput: ' + JSON.stringify(beadCounts) +
        '\nSum of squares of moves: ' + solution.sumSquares +
        '\nSum of moves: ' + solution.sumMoves +
        '\nMoves[slot][bead]: ' + JSON.stringify(solution.movesPerSlotPerBead);
};
Comma-separated list of number of beads per slot:<br/>
<input id="input" size="40" value="3,0,1,0,1,0,2,2,2,1,1,0,1,0">
<button id="randomize">Randomize</button><br/>
<button id="calculate">Find optimal spread</button></br>
<pre id="output"></pre>

此代码片段采用一个数组,其中每个索引代表一个槽,值代表该槽中的珠子数量。它输出原始数组、可选解决方案的平方和以及珠子的移动列表。

问题中示例问题的输出是:

Input: [3,0,1,0,1,0,2,2,2,1,1,0,1,0]  
Sum of squares of moves: 12  
Sum of moves: -2  
Moves[slot][bead]: [[-1,0,1],[],[0],[],[-1],[],[-2,-1],[-1,0],[0,1],[1],[1],[],[0],[]]

注意:这不是问题的答案,因为它允许逆时针移动。有关答案,请参阅此回复的前半部分。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

解决这个分配珠子难题的算法? 的相关文章

  • 如何从 Trie 中检索给定长度的随机单词

    我有一个简单的 Trie 用来存储大约 80k 长度为 2 15 的单词 它非常适合检查字符串是否是单词 但是 现在我需要一种获取给定长度的随机单词的方法 换句话说 我需要 getRandomWord 5 来返回 5 个字母的单词 所有 5
  • 优先连接,Matlab 中的复杂网络

    大家好 我现在正在 MATLAB 中研究优先附件模型 在理解以下内容时遇到一些困难 假设我一开始有 4 个节点 连接如下 time 0 1 lt gt 2 3 lt gt 4 在下一个时间步骤中 我添加一个节点和 4 个连接 然后添加另一个
  • 2D形状识别与解析算法

    我正在寻找一种算法 用于从给定的一组 x y 点检测简单形状 如矩形 三角形 正方形和圆形 我还在寻找一种方法 一旦检测到 将路径转换为更干净的形状 我已经查遍了互联网 但没有找到任何 简单 的方法 几乎所有这些对于我的简单实现来说都是高级
  • 查找一个二维矩阵是否是另一个二维矩阵的子集

    最近我参加了一个黑客马拉松 我了解到一个问题 试图在 2d 矩阵中找到网格形式的模式 模式可以是 U H 和 T 并由 3 3 矩阵表示 假设我想展示 H 和 U 1 0 1 1 0 1 1 1 1 gt H 1 0 1 gt U 1 0
  • 基于 2 个输入的伪随机数生成器 [关闭]

    Closed 这个问题需要细节或清晰度 help closed questions 目前不接受答案 我需要根据 2 个输入值 X 和 Y 生成一个伪随机数 给定相同的 X 和 Y 值 我需要得到相同的结果 结果应介于 0 和 1 之间 含
  • 依次构建完整的 B 树

    如果我有一组排序的数据 我想以最适合顺序读取和随机查找的方式将其存储在磁盘上 那么 B 树 或其中一个变体 似乎是一个不错的选择 假设该数据集并不全部适合 RAM 问题是可以从一组排序的数据构建完整的 B 树而不进行任何页面拆分吗 这样排序
  • 如何检测图像是否像素化

    之前有人在 SO 上提出过这样的问题 在Python中检测像素化图像 https stackoverflow com questions 12942365 detecting a pixelated image in python还有关于q
  • 测量数组的“无序”程度

    给定一个值数组 我想找到总 分数 其中每个元素的分数是数组中出现在其之前的具有较小值的元素的数量 e g values 4 1 3 2 5 scores 0 0 1 1 4 total score 6 O n 2 算法很简单 但我怀疑可以通
  • 关于逻辑/算法的想法以及如何防止线程写入 Sql Server 中的竞争

    我有以下逻辑 public void InQueueTable DataTable Table int incomingRows Table Rows Count if incomingRows gt RowsThreshold async
  • 寻找簇的中心

    我有以下问题 进行抽象以找出关键问题 我有 10 个点 每个点与其他点有一定距离 我想要 能够找到簇的中心 即与其他点的成对距离最小的点 令 p j p k 表示点 j 和 k 之间的成对距离p i 是簇的中心点 iff p i s t m
  • 数字求和的算法?

    我正在寻找一种数字求和的算法 让我概述一下基本原则 假设你有一个号码 18268 1 8 2 6 8 25 2 5 7 7 是我们的最终数字 它基本上是将整个数字中的每个数字相加 直到我们得到一个 也称为 核心 数字 它经常被命理学家使用
  • 字符串的渐进单词组合

    我需要获得字符串的渐进单词组合 例如 这是字符串 输出 这是字符串 这是 这个字符串 是字符串 这 是 细绳 你知道类似的算法吗 我需要php语言 谢谢 这是解决您问题的简单代码 我将每个字符串递归地连接到数组中的其余字符串 string
  • 计算给出数组中最小标准差的子集

    让我们有一个大小的向量N 例如 x rand N 1 我想计算长度子集的最小标准差K在向量中 When N and K很小 很容易找到最好的子集 因为我可以使用nchoosek N K 枚举所有可能的子集 但是当值N and K比我们说的要
  • 从对列表创建邻接列表类型结构

    在 C 中 我有 class Pair int val1 int val2 我有一个来自以下来源的配对列表 List
  • 查找重叠事件/时间的算法

    在处理自定义日历时 我不知道如何找到与任何其他时间段重叠的时间段 时段从 0 点至 720 点 上午 9 点至晚上 9 点 每个像素代表一分钟 var events id 1 start 0 end 40 an event from 9 0
  • 加权图的 BFS 算法 - 寻找最短距离

    我看过很多帖子 即 post1 https stackoverflow com questions 30409493 using bfs for weighted graphs post2 https cs stackexchange co
  • 查找两个大小为 n 的数组中第 n 大数的算法

    我有这个问题 给定两个大小为 n 的排序列表 存储在数组中 找到 O log n 计算并集中第 n 大元素的算法 两个列表 我可以看到这里可能有一个技巧 因为它需要第 n 个最大的元素 并且数组的大小也是 n 但我不知道它是什么 我在想我可
  • n的渐近增长选择下限(n/2)

    如何找到 n select Floor n 2 的渐近增长 我试过 使用扩展并得到它等于 n n 1 floor n 2 1 n floor n 2 知道我该如何从那里去吗 感谢任何帮助 更喜欢提示而不是答案 我同意上面的答案 但想提供更多
  • 大小为 n 的数组,其中一个元素 n/2 次

    给定一个由 n 个整数组成的数组 其中一个元素出现超过 n 2 次 我们需要在线性时间和恒定的额外空间中找到该元素 YAAQ 又一个数组问题 我有一种偷偷的怀疑 这类似于 在 C 中 We don t need an array publi
  • 如何从列中创建对称矩阵?

    例如 我想转动以下列 90 175 600 650 655 660 代入矩阵 90 175 600 650 655 660 175 600 650 655 660 655 600 650 655 660 655 650 650 655 66

随机推荐