Reputation: 465
There are K
points on a circle that represent the location of the treasures. N
people want to share the treasures. You want to divide the treasure fairly among all of them such that the difference between the person having the maximum value and the person having the minimum value is as minimum as possible.
For example if there are 4 treasures and 2 people as shown in the figure, then the optimal way of dividing would be
(6, 10) and (11, 3) => with a difference of 2.
1 <= n <= 25
1 <= k <= 50
How do I approach solving this problem? I planned to calculate the mean of all the points and keep adding the resources until they are lesser than the mean for each person. But as obvious as it is, it will not work in all cases.
I'd be glad if someone throws some light.
Upvotes: 6
Views: 590
Reputation: 23955
Consider that for each k
, we can pair a sum growing from A[i]
to the left (sum A[i-j..i]
) with all available intervals recorded for f(k-1, i-j-1)
and update them: for each interval, (low, high)
, if the sum is greater than high
, then new_interval = (low, sum)
and if the sum is lower than low
, then new_interval = (sum, high)
; otherwise, the interval stays the same. For example,
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
Now, we can make this iteration much more efficient by recognizing and pruning intervals during the previous k
round.
Watch:
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)
For k = 2
, we are now left with the following pruned record:
{
k: 2,
n: {
1: (1,5),
2: (2,5),
3: (3,5),
4: (5,6),
5: (6,7)
}
}
We've cut down the iteration of k = 3
from a list of n choose 2
possible splits to n
relevant splits!
The general algorithm applied to 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)
The answer is 5
paired with record[2][3] = (3,5)
, yielding the updated interval, (3,5)
. I'll leave the pruning logic for the reader to work out. If we wanted to continue, here's the pruned list for k = 3
{
k: 3
n: {
2: (1,5),
3: (1,5),
4: (3,5),
5: (3,5)
}
}
Upvotes: 0
Reputation: 3543
No standard type of algorithm ( greedy, divide and conquer etc ) exists for this problem.
You would have to check each and every combination of (resource, people)
and pick the answer. Once you have solved the problem using recursion, you can throw DP
to optimize the solution.
The curx of the solution is:
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
Here is the javascript version of the answer.
I have commented it to try to explain the logic as well:
// 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(", "), "]"));
Each problem at index
i
creates exactlyN
copies of itself and we have totalK
indices, the time complexity of the problem to solve isO(K^N)
We can definitely do better by throwing memoization.
Here comes the tricky part:
If we have a distribution of treasure for one user, the minimum difference of the distributions of treasure among users will be the same.
In out case, bitset[i]
represents the distribution for ith
user.
Thus, we can memoize the results for the bitset
of the user.
One you realize that, coding that is easy:
// 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);
We can definitely improve the space used by not caching the whole bitset. Removing the bitset from cahce is trivial.
Upvotes: 0
Reputation: 4888
You can do brute-force for O(k^n) or dp for O(k^{2}*MAXSUM^{k — 1}).
dp[i][val1][val2]...[val k -1] is it possible to distribute first k items, so first have val1, second — val2 and so on. There are k * MAXSUM(k - 1) states and you need O(k) to do step, you simply choose who takes ith item.
I dont think it's possible to solve it faster.
Upvotes: 0
Reputation: 11968
So say we fix x, y as the min max allowed for the treasure. I need to figure out if we can get a solution in these constraints.
For that I need to traverse the circle and create exactly N segments with sums between x and y. This I can solve via dynamic programming, a[i][j][l] = 1 if I can split the elements between i and j into l whose sums are between x and y (see above). To compute it we can evaluate 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). To handle the circle look for n-1 segments that cover enough elements and the remaining difference remains between x and y.
So naive solution is O(total_sum^2) to select X and Y plus O(K^3) to iterate over i,j,l and another O(K) to find a q and another O(K) to get the sum. That's a total of O(total_sum^2 * K^5) which likely is too slow.
So we need to compute sums a lot. So let's precompute a partial sums array sums[w] = sum(elements between pos 0 and pos w). So to get the sum between q and j you only need to compute sums[j] - sums[q-1]. This takes care of O(K).
To compute the a[i][j][l]. Since the treasures are always positive, if a partial sum is too small we need to grow the interval, if the sum is too high we need to shrink the interval. Sine we fixed a side of the interval (at j) we can only move the q. We can use binary search to find the closes t and the furthest q that allow us to be between x and y. Let's call them low_q (the closest to j, lowest sum) and high_q (far from j, largest sum). If low_q < i then the interval is too small so the value is 0. So now we need to check if there's a 1 between max(high_q, i) and low_q. The max is to make sure we don't look outside of the interval. To do the check we can precompute again partial sums to count how many 1s are in out interval. We only need to do this once per level so it will be amortized O(1). So, if we did everything right this will be O(K^3 logK).
We still have the total_sum^2 in front. Let's say we fix X. If for a given y we have a solution you might be able to find also a smaller y that still has a solution. If you can't find a solution for a given y then you won't be able to find a solution for any smaller value. So we can now do a binary search on y.
So this is now O(total_sum *log(total_sum) * K^3 * logK).
Other optimization would be to not raise i if the sum(0-> i- 1) > x. You might not want to check for values of x > total_sum/K since that's the ideal minimum value. This should cancel out one of the K is the complexity.
There might be other things that you can do, but I think this will be fast enough for your constraints.
Upvotes: 1