假设您有一个圆圈(如下所示),其中有 N 个点,您在插槽中分布了 N 个珠子。
这是一个例子:
每个珠子可以顺时针移动 X 个插槽,这需要花费 X ^ 2 美元。 您的目标是最终在每个插槽中放置一个珠子。 您需要花费的最少金额是多少?
此问题的更有趣的变体:分配珠子难题的算法(2)?
这是一个例子:
![enter image description here](https://istack.dev59.com/BXzaD.webp)
此问题的更有趣的变体:分配珠子难题的算法(2)?
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
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美元。
如果去除顺时针移动的要求,移动可以朝任何方向进行,这个问题并没有被提出,但我认为这是一个有趣的变体。
以下是适用于该情况的其他观察结果:
为了找到新配置的平方和,不需要实际执行循环:
假设我们有三个珠子和三个插槽,并且通过将它们分别移动x,y和z个插槽来将珠子移动到目标插槽。例如,如果它们都在插槽0中,我们可以获得0、1、2(甚至-1、0、1或5、6、7,如果我们想夸张)的x,y,z值。
平方和为:
x²+y²+z²
(x+1)²+(y+1)²+(z+1)²
或者:
x²+y²+z² +3 +2(x+y+z)
n + 2.sum(moves)
因此,该算法可以利用这一点,并快速计算从一个周期得出的解的平方和。这可以在随后的周期中重复进行。
最后,每个连续周期(解决方案)的平方和将是一个抛物线形状的函数,即一旦找到一个局部最小值,就不需要再寻找另一个了;不存在多个局部最小值。我们可以从上面对于sum(moves)
增加的公式看出这一点。至多,相邻两个周期可能会有相等的平方和。
以下是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],[]]
clear;
%// inputBps (beads per space) will be our starting point.
%// inputBps=[2 0 3 0 0 0 4 0 1 0 1 0 1 2];
%// inputBps=[2 0 2 0 0 2];
%// inputBps=[1 1 1 1 1];
inputBps=[3 0 1 0 1 0 2 2 2 1 1 0 1 0];
%// This first block of code finds a good place to start.
%// We find a candidate for a bead that should not move.
okStart = 1;
stack = 0;
for i=1:length(inputBps)
if inputBps(i)>1
stack = stack + inputBps(i)-1;
end
if inputBps(i)==0 && stack<=0
okStart = i+1;
end
if inputBps(i)==0 && stack>0
stack=stack-1;
end
end
%// This lets us start at the space we found above.
bps = [inputBps(okStart:end) inputBps(1:okStart-1)];
sum=0;
nextFree=1;
for k=1:length(bps)
%// firstMoves are the moves that all the beads
%// in a space have to take because they were "forced out."
firstMoves = nextFree-k;
%// Say firstMoves=3 and bps(k)=2. Then our our two beads
%// moved three spaces and four spaces, like this:
%// sum = sum + 3^2 + 4^2. Rewriting:
%// sum = sum + (1^2 + 2^2 + 3^2 + 4^2) - (1^2 + 2^2)
sum = sum + squares( bps(k) + firstMoves - 1 ) - squares( firstMoves-1 );
%// Now calculate the next space that can accept a bead.
nextFree = nextFree+bps(k);
end
.
function [ y ] = squares( x )
%SQUARES Returns sqaure payramid of input
y = x*(x+1)*(2*x+1) / 6 ;
end
问题的结果如下:
sum =
60