Got this task from a game.
Farmer has
a field of constant size 16
a daily water supply which he can improve while progressing the game. For a given day it is 100, in a few days it may become 130.
around 50 types of crops. Each crop has daily yield (in gold coins) and water consumption.
Each crop type must be unique (planted once per day)
The goal is to get maximum gold per day.
Which breaks down to finding 1-16 crops whose sum of water consumption is no more than water supply (input parameter, e.g. 100) and yield sum is maximum.
Crop types are generated randomly with yield range 10-50000 and consumption range 1-120.
Brute force needs 50!/(50-16)! iterations - about 10e27.
Are there any more optimal ways to find max output?
This is called the Knapsack problem. Here's pseudocode lifted from Wikipedia.
// Input:
// Values (stored in array v)
// Weights (stored in array w)
// Number of distinct items (n)
// Knapsack capacity (W)
// NOTE: The array "v" and array "w" are assumed to store all relevant values starting at index 1.
array m[0..n, 0..W];
for j from 0 to W do:
m[0, j] := 0
for i from 1 to n do:
m[i, 0] := 0
for i from 1 to n do:
for j from 0 to W do:
if w[i] > j then:
m[i, j] := m[i-1, j]
else:
m[i, j] := max(m[i-1, j], m[i-1, j-w[i]] + v[i])
https://en.wikipedia.org/wiki/Knapsack_problem
https://medium.com/#fabianterh/how-to-solve-the-knapsack-problem-with-dynamic-programming-eb88c706d3cf
Related
I have 2 problems that derive from a simple problem. I'll explain the simple one with the solution I found and after that the modified problem.
Suppose there is a game with 2 players, A and B and a list of
positive integers. Player A starts by taking out a number from the list, player
B does the same and so on after the there are no longer numbers in the
list. Both players sum up the picked numbers. The goal
for each player is to maximize the difference between his sum and
opponent's sum, which is the score. The question is what is the
maximum score player A can obtain if both players play in an optimal
manner.
Now, for this I figured out that the optimal strategy for each player is to take the biggest number at each step, the pseudocode is the following:
sumA = 0
sumB = 0
list = [1, 5, 3, 7, 9]
while list IS NOT EMPTY:
val = pop_max(list)
sumA = sumA + val
if list IS NOT EMPTY:
val = pop_max(list)
sumB = sumB + val
scoreA = sumA - sumB
print scoreA
This can run in O(n) or O(n*log(n)) depending how the numbers from list are sorted.
The following 2 modification:
At the beginning of the game player A should remove K numbers from the list. If he does this in an optimal manner and after that the games is the initial one, what is the maxim score he can obtain?
and
At each step the players can pick the left-most or the right-most number from the list. Again they play in an optimal manner. Which is the maximum score player A can obtain?
For the second modification I can think of a brute-force approach, i.e. computing the tree of all possibilities, but this does not work for big input data. I believe that there is some kind of DP algorithm.
For the first modification I can't think of an idea.
Can someone help with some algorithm ideas for the 2 modifications?
[LATTER EDIT]
The solution for the 2nd modification can be found here https://www.geeksforgeeks.org/optimal-strategy-for-a-game-dp-31/ It is DP.
Here is the post for the 2nd modification, which is
At each step the players can pick the left-most or the right-most number from the list. Again they play in an optimal manner. Which is the maximum score player A can obtain?
The solution is based on DP. For the sub-problem (i-j) i.e. v[]i, v[i+1], ..., v[j] there are two choices:
The user chooses the i-th element with value v[i]: The opponent either chooses (i+1)-th element or j-th element. The opponent intends to choose the element which leaves the user with minimum value. i.e. The user can collect the value v[i] + min(F(i+2, j), F(i+1, j-1))
The user chooses the j-th element with value v[j]: The opponent either chooses i-th element or (j-1)-th element. The opponent intends to choose the element which leaves the user with minimum value.
i.e. The user can collect the value v[j] + min(F(i+1, j-1), F(i, j-2))
Following is recursive solution that is based on above two choices. We take the maximum of two choices.
F(i, j) represents the maximum value the user can collect from i-th coin to j-th coin.
F(i, j) = Max(v[i] + min(F(i+2, j), F(i+1, j-1)), v[j] + min(F(i+1, j-1), F(i, j-2)))
Base Cases
F(i, j) = v[i] If j == i
F(i, j) = max(v[i], v[j]) If j == i+1
Here is a pice of code in Python that solves it
def optimalStrategyOfGame(arr, n):
# Create a table to store solutions of subproblems
table = [[0 for i in range(n)] for i in range(n)]
# Fill table using above recursive formula. Note that the table is
# filled in diagonal fashion from diagonal elements to table[0][n-1] which is the result.
for gap in range(n):
for j in range(gap, n):
i = j - gap
# Here x is value of F(i+2, j), y is F(i+1, j-1) and z is
# F(i, j-2) in above recursive formula
x = 0
if((i + 2) <= j):
x = table[i + 2][j]
y = 0
if((i + 1) <= (j - 1)):
y = table[i + 1][j - 1]
z = 0
if(i <= (j - 2)):
z = table[i][j - 2]
table[i][j] = max(arr[i] + min(x, y), arr[j] + min(y, z))
return table[0][n - 1]
[SOURCE] https://www.geeksforgeeks.org/optimal-strategy-for-a-game-dp-31/
I'm working on a program to solve a variant of the 0/1 Knapsack problem.
The original problem is described here: https://en.wikipedia.org/wiki/Knapsack_problem.
In case the link goes missing in the future, I will give you a summary of the 0/1 Knapsack problem (if you are familiar with it, jump this paragraph):
Let's say we have n items, each with weight wi and value vi. We want to put items in a bag, that supports a maximum weight W, so that the total value inside the bag is the maximum possible without overweighting the bag. Items cannot have multiple instances (i.e., we only have one of each). The objective of the problem is to maximize SUM(vi.xi) so that SUM(wi.xi) <= W and xi = 0, 1 (xi represents the state of an item being or not in the bag).
For my case, there are small differences in both conditions and objective:
The weight of all items is 1, wi = 1, i = 1...n
I always want to put exactly half the items in the bag. So, the maximum weight capacity of the bag is half (rounded up) of the number of items.W = ceil[n/2] or W = floor[(n+1)/2].
Also, the weight inside the bag must be equal to its maximum capacity SUM(wi.xi) = W
Finally, instead of maximizing the value of the items inside the bag, the objective is that the value of the items inside is as close as possible to the value of the items outside. Hence, my objective is to minimize |SUM(vi.-xi) - SUM[vi(1-xi)]|, which simplifies into something like minimize |SUM[vi(2xi - 1)]|.
Now, there is a pseudo-code for the original 0/1 Knapsack problem in the Wikipedia page above (you can find it on the bottom of this text), but I am having trouble adapting it to my scenario. Can someone help? (I am not asking for code, just for an idea, so language is irrelevant)
Thanks!
Wikipedia's pseudo-code for 0/1 Knapsack problem:
Assume w1, w2, ..., wn, W are strictly positive integers. Define
m[i,w] to be the maximum value that can be attained with weight less
than or equal to w using items up to i (first i items).
We can define m[i,w] recursively as follows:
m[0, w]=0
m[i, w] = m[i-1, w] if wi > w (the new item is more than the current weight limit)
m[i, w]= max(m[i-1, w], m[i-1, w-wi] + vi) if wi <= w.
The solution can then be found by calculating m[n,W].
// Input:
// Values (stored in array v)
// Weights (stored in array w)
// Number of distinct items (n)
// Knapsack capacity (W)
for j from 0 to W do:
m[0, j] := 0
for i from 1 to n do:
for j from 0 to W do:
if w[i-1] <= j then:
m[i, j] := max(m[i-1, j], m[i-1, j-w[i-1]] + v[i-1])
else:
m[i, j] := m[i-1, j]
Thanks to #harold, it seems like this problem is not a Knapsack problem, but a Partition problem. Part of the pseudo-code I was seeking is in the corresponding Wikipedia page: https://en.wikipedia.org/wiki/Partition_problem
EDIT: well, actually, Partition problem algorithms tell you whether a Set of items can be partitioned in 2 sets of equal value or not. Suppose it can't, you have approximation algorithms, which say whether you can have the set partiotioned in 2 sets with the difference their values being lower than d.
BUT, they don't tell you the resulting sub-sets, and that's what I was seeking.
I ended up finding a question here asking for that (here: Balanced partition), with a code example which I have tested and works fine.
In http://en.wikipedia.org/wiki/Knapsack_problem, the DP is:
// Input:
// Values (stored in array v)
// Weights (stored in array w)
// Number of distinct items (n)
// Knapsack capacity (W)
for j from 0 to W do
m[0, j] := 0
end for
for i from 1 to n do
for j from 0 to W do
if w[i] <= j then
m[i, j] := max(m[i-1, j], m[i-1, j-w[i]] + v[i])
else
m[i, j] := m[i-1, j]
end if
end for
end for
I think switching the order for the weight loop and the number loop does not impact the optimal solution. Is this right? Say
for j from 0 to W do
for i from 1 to n do
Thanks.
You are correct. The value of m[i,j] depends only on values with both smaller is and js. The situation where changing the lop order matters is when one of the elements can increase. For example, if m[2,2] depends on m[1,3] then we need calculate the first row comlpetely before moving to the second row.
I need to modify wiki's knapsack pseudocode for my homework so it checks whether you can achieve exact weight W in the knapsack or not. Number of items is unlimited and you the value not important. I am thinking to add a while loop under j>-W[j] to check how many same items would it fit. Will that work?
Thanks
// Input:
// Values (stored in array v)
// Weights (stored in array w)
// Number of distinct items (n)
// Knapsack capacity (W)
for w from 0 to W do
m[0, w] := 0
end for
for i from 1 to n do
for j from 0 to W do
if j >= w[i] then
m[i, j] := max(m[i-1, j], m[i-1, j-w[i]] + v[i])
else
m[i, j] := m[i-1, j]
end if
end for
end for
If I'm not missing anything, if you are referring to this wiki article
In the modified version, your values array shall be the same as your w array. After calculating m[n,W], simply check whether it is equal to W.
EDIT:
If you have unlimited number of items, then you are dealing with Unbounded Knapsack Problem. This is a different problem and the same article gives the dynamic programming implementation for solving it.
I was reading wikipedia regarding the 0-1 knapsack problem. I just want to clarify a couple things. I have two questions:
http://en.wikipedia.org/wiki/Knapsack_problem#0.2F1_Knapsack_Problem
I encountered this pseudo-code:
// Input:
// Values (stored in array v)
// Weights (stored in array w)
// Number of distinct items (n)
// Knapsack capacity (W)
for w from 0 to W do
m[0, w] := 0
end for
for i from 1 to n do
for j from 0 to W do
if j >= w[i] then
m[i, j] := max(m[i-1, j], m[i-1, j-w[i]] + v[i])
else
m[i, j] := m[i-1, j]
end if
end for
end for
Specifically for this part:
if j >= w[i] then
m[i, j] := max(m[i-1, j], m[i-1, j-w[i]] + v[i])
1) Correct me if I'm wrong, but shouldn't it be:
m[i, j] := max(m[i-1, j], m[i-1, j-w[i]] + v[i], m[i,j-w[i]] + v[i])
?
Or if not, can someone explain me why it's not needed?
...
2) And I also have another question, if say I want to optimize this a bit. Would it be wise to have the loop "for j from 0 to W" increment by the GCD of all the weights of the items (i.e. GCD of the values stored in array w). (I'm thinking just code-wise right now when I'm about to implement it).
1) When you add m[i,j-w[i]] + v[i], you're allowing the same item i to be selected more than once, thus it is no longer 0/1 Knapsack - it becomes a Knapsack problem with unlimited amounts of each item.
2) Yes, but this GCD usually comes down to 1 on real instances, thus not worth bothering with for general cases, unless you know beforehand that your data would benefit from it. (In this case, you'd actually want to divide all your data by the GCD, and keep the original algorithm incrementing 1 at a time, then multiply the final result by the GCD. This would save you memory as well, but your Knapsack capacity must also be divisible by such GCD)