Calculate the winning strategy of a subtraction game - algorithm

Problem:
Given 100 stones, two players alternate to take stones out. One can take any number from 1 to 15; however, one cannot take any number that was already taken. If in the end of the game, there is k stones left, but 1 through k have all been previously taken, one can take k stones. The one who takes the last stone wins. How can the first player always win?
My Idea
Use recursion (or dynamic programming). Base case 1, where player 1 has a winning strategy.
Reducing: for n stones left, if palyer 1 takes m1 stones, he has to ensure that for all options player 2 has (m2), he has a winning strategy. Thus the problem is reduced to (n - m1 - m2).
Follow Up Question:
If one uses DP, the potential number of tables to be filled is large (2^15), since the available options left depend on the history, which has 2^15 possibilities.
How can you optimize?

Assuming that the set of numbers remaining can be represented as R, the highest number remaining after your selection can be represented by RH and the lowest number remaining can be RL, the trick is to use your second-to-last move to raise the number to <100-RH, but >100-RH-RL. That forces your opponent to take a number that will put you in winning range.
The final range of winning, with the total number that you create with your second-to-last move, is:
N < 100-RH
N > 100-RH-RL
By observation I noted that RH can be as high as 15 and as low as 8. RL can be as low as 1 and as high as 13. From this range I evaluated the equations.
N < 100-[8:15] => N < [92:85]
N > 100-[8:15]-[1:13] => N > [92:85] - [1:13] => N > [91:72]
Other considerations can narrow this gap. RL, for instance, is only 13 in an edge circumstance that always results in a loss for Player A, so the true range is between 72 and 91. There is a similar issue with RH and the low end of it, so the final ranges and calculations are:
N < 100-[9:15] => N < [91:85]
N > 100-[9:15]-[1:12] => N > [91:85] - [1:12] => N > [90:73]
[90:73] < N < [91:85]
Before this, however, the possibilities explode. Remember, this is AFTER you choose your second-to-last number, not before. At this point they are forced to choose a number that will allow you to win.
Note that 90 is not a valid choice to win with, even though it might exist. Thus, the maximum it can be is 89. The real range of N is:
[88:73] < N < [90:85]
It is, however, possible to calculate the range of the number that you're using to put your opponent in a no-win situation. In the situation you find yourself in, the lowest number or the highest number might be the one you chose, so if RHc is the highest number you can pick and RLc is the lowest number you can pick, then
RHc = [9:15]
RLc = [1:12]
With this information, I can begin constructing a relative algorithm starting from the end of the game.
N*p* - RHp - RLp < Np < N*p* - RHp, where p = iteration and *p* = iteration + 1
RHp = [8+p:15]
RLp = [1:13-p]
p = -1 is your winning move
p = 0 is your opponent's helpless move
p = 1 is your set-up move
Np is the sum of that round.
Thus, solving the algorithm for your set-up move, p=1, you get:
N*p* - [9:15] - [1:12] < Np < N*p* - [9:15]
100 <= N*p* <= 114
I'm still working out the math for this, so expect adjustments. If you see an error, please let me know and I'll adjust appropriately.

Here is a simple, brute force Python code:
# stoneCount: number of stones to start the game with
# possibleMoves: which numbers of stones may be removed? (*sorted* list of integers)
# return value: signals if winning can be forced by first player;
# if True, the winning move is attached
def isWinningPosition(stoneCount, possibleMoves):
if stoneCount == 0:
return False
if len(possibleMoves) == 0:
raise ValueError("no moves left")
if stoneCount in possibleMoves or stoneCount < possibleMoves[0]:
return True,stoneCount
for move in possibleMoves:
if move > stoneCount:
break
remainingMoves = [m for m in possibleMoves if m != move]
winning = isWinningPosition(stoneCount - move, remainingMoves)
if winning == False:
return True,move
return False
For the given problem size this function returns in less than 20 seconds on an Intel i7:
>>> isWinningPosition(100, range(1,16))
False
(So the first play cannot force a win in this situation. Whatever move he makes, it will result in a winning position for the second player.)
Of course, there is a lot of room for run time optimization. In the above implementation many situations are reached and recomputed again and again (e.g. when the first play takes one stone and the second player takes two stones this will put the first player into the same situation as when the number of stones taken by each player are reversed). So the first (major) improvement is to memorize already computed situations. Then one could go for more efficient data structures (e.g. encoding the list of possible moves as bit pattern).

Related

FireHose (S3) from CCC

This grade 11 problem has been bothering me since 2010 and I still can't figure out/find a solution even after university.
Problem Description
There is a very unusual street in your neighbourhood. This street
forms a perfect circle, and the circumference of the circle is
1,000,000. There are H (1 ≤ H ≤ 1000) houses on the street. The
address of each house is the clockwise arc-length from the
northern-most point of the circle. The address of the house at the
northern-most point of the circle is 0. You also have special firehoses
which follow the curve of the street. However, you wish to keep the
length of the longest hose you require to a minimum. Your task is to
place k (1 ≤ k ≤ 1000) fire hydrants on this street so that the maximum
length of hose required to connect a house to a fire hydrant is as
small as possible.
Input Specification
The first line of input will be an integer H, the number of houses. The
next H lines each contain one integer, which is the address of that
particular house, and each house address is at least 0 and less than
1,000,000. On the H + 2nd line is the number k, which is the number of
fire hydrants that can be placed around the circle. Note that a fire
hydrant can be placed at the same position as a house. You may assume
that no two houses are at the same address. Note: at least 40% of the
marks for this question have H ≤ 10.
Output Specification
On one line, output the length of hose required
so that every house can connect to its nearest fire hydrant with that
length of hose.
Sample Input
4
0
67000
68000
77000
2
Output for Sample Input
5000
Link to original question
I can't even come up with a brutal force algorithm since the placement might be float number. For example if the houses are located in 1 and 2, then the hydro should be placed at 1.5 and the distance would be 0.5
Here is quick outline of an answer.
First write a function that can figures out whether you can cover all of the houses with a given maximum length per hydrant. (The maximum hose will be half that length.) It just starts at a house, covers all of the houses it can, jumps to the next, and ditto, and sees whether you stretch. If you fail it tries starting at the next house instead until it has gone around the circle. This will be a O(n^2) function.
Second create a sorted list of the pairwise distances between houses. (You have to consider it going both ways around for a single hydrant, you can only worry about the shorter way if you have 2+ hydrants.) The length covered by a hydrant will be one of those. This takes O(n^2 log(n)).
Now do a binary search to find the shortest length that can cover all of the houses. This will require O(log(n)) calls to the O(n^2) function that you wrote in the first step.
The end result is a O(n^2 log(n)) algorithm.
And here is working code for all but the parsing logic.
#! /usr/bin/env python
def _find_hoses_needed (circle_length, hose_span, houses):
# We assume that houses is sorted.
answers = [] # We can always get away with one hydrant per house.
for start in range(len(houses)):
needed = 1
last_begin = start
current_house = start + 1 if start + 1 < len(houses) else 0
while current_house != start:
pos_begin = houses[last_begin]
pos_end = houses[current_house]
length = pos_end - pos_begin if pos_begin <= pos_end else circle_length + pos_begin - pos_end
if hose_span < length:
# We need a new hose.
needed = needed + 1
last_begin = current_house
current_house = current_house + 1
if len(houses) <= current_house:
# We looped around the circle.
current_house = 0
answers.append(needed)
return min(answers)
def find_min_hose_coverage (circle_length, hydrant_count, houses):
houses = sorted(houses)
# First we find all of the possible answers.
is_length = set()
for i in range(len(houses)):
for j in range(i, len(houses)):
is_length.add(houses[j] - houses[i])
is_length.add(houses[i] - houses[j] + circle_length)
possible_answers = sorted(is_length)
# Now we do a binary search.
lower = 0
upper = len(possible_answers) - 1
while lower < upper:
mid = (lower + upper) / 2 # Note, we lose the fraction here.
if hydrant_count < _find_hoses_needed(circle_length, possible_answers[mid], houses):
# We need a strictly longer coverage to make it.
lower = mid + 1
else:
# Longer is not needed
upper = mid
return possible_answers[lower]
print(find_min_hose_coverage(1000000, 2, [0, 67000, 68000, 77000])/2.0)

Game of flipping two consecutive positives to negatives

I was asked this questions in one of my interviews. The questions goes like this
You have a string of '+' and '-' ( eg. ++----++++++-+--+ ). There are two players Player 1 and Player 2. At each turn one of the player can choose any two consecutive '+' i.e. ++ and flip them into --. So if the initial string is ++----++++++-+--+ and then the player has the following 6 choices (2 - 7) .(first one is for reference).
++----++++++-+--+
------++++++-+--+
++------++++-+--+
++----+--+++-+--+
++----++--++-+--+
++----+++--+-+--+
++----++++---+--+
The player takes turn one by one.
The player which plays the last move will win ( or lose - doesn't make a difference).
Given a initial string and if player 1 takes the first turn, we have to tell who wins?
Now this seems like a classical game theory problem where each player tries to play optimally and at each step plays a move that moves him to a winning position.
Any ideas on how can I approach this to solve ?
PS - Interested more in approach than in solving. I have read http://www.codechef.com/wiki/tutorial-game-theory but couldn't apply the same logic here.
We can use Grundy function here, because after converting ++ to -- the game is divided into a sum of 2 separate games: to the left from -- and to the right. Let's assume that g(l, r) is the value of Grundy function for the [l, r] interval of the given string. To compute it, we can try to change ++ to -- in all possible positions, store all g(l, k - 1) xor g(k + 2, r)(where k is a position where ++ starts) values and choose the smallest non-negative integer that is not among them. The value for the base case(when there are no possible moves) is either 0 or 1(depending on whether the last player loses or wins). The first player wins if and only if g(0, n - 1)(n is the lenght of the input string) is non-zero. This solution has O(n^3) time complexity(there are O(n^2) possible (l, r) pairs and we can compute thr answer in linear time for each of them).

How to efficiently detect a tie early in m,n,k-game (generalized tic-tac-toe)?

I'm implementing an m,n,k-game, a generalized version of tic-tac-toe, where m is the number of rows, n is the number of columns and k is the number of pieces that a player needs to put in a row to win. I have implemented a check for a win, but I haven't figured out a satisfactory way to check before the board is full of pieces, if no player can win the game. In other words, there might be empty slots on the board, but they cannot be filled in such a way that one player would win.
My question is, how to check this efficiently? The following algorithm is the best that I can think of. It checks for two conditions:
A. Go over all board positions in all 4 directions (top to bottom, right to left, and both diagonal directions). If say k = 5, and 4 (= k-1) consecutive empty slots are found, stop checking and report "no tie". This doesn't take into account for example the following situation:
OX----XO (Example 1)
where a) there are 4 empty consecutive slots (-) somewhere between two X's, b) next it is O's turn, c) there are less than four other empty positions on the board and no player can win by putting pieces to those, and d) it is not possible to win in any other direction than horizontally in the shown slots either. Now we know that it is a tie because O will eventually block the last winning possibility, but erroneously it is not reported yet because there are four consecutive empty slots. That would be ok (but not great). Checking this condition gives a good speed-up at the beginning when the checking algorithm usually finds such a case early, but it gets slower as more pieces are put on the board.
B. If this k-1-consecutive-empty-slots-condition isn't met, the algorithm would check the slots again consecutively in all 4 directions. Suppose we are currently checking from left to right. If at some point an X is encountered and it was preceded by an O or - (empty slot) or a board border, then start counting the number of consecutive X's and empty slots, counting in this first encountered X. If one can count to 5, then one knows it is possible for X to win, and "no tie" is reported. If an O preceded by an X is encountered before 5 consecutive X's, then X cannot win in those 5 slots from left to right starting from where we started counting. For example:
X-XXO (Example 2)
12345
Here we started checking at position 1, counted to 4, and encountered an O. In this case, one would continue from the encountered O in the same way, trying to find 5 consecutive O's or empty slots this time. In another case when counting X's or empty slots, an O preceded by one or more empty slots is encountered, before counting to 5. For example:
X-X-O (Example 3)
12345
In this case we would again continue from the O at position 5, but add to the new counter (of consecutive O's or empty slots) the number of consecutive empty slots that preceded O, here 1, so that we wouldn't miss for example this possible winning position:
X-X-O---X (Example 4)
In this way, in the worst case, one would have to go through all positions 4 times (4 directions, and of course diagonals whose length is less than k can be skipped), giving running time O(mn).
The best way I could think of was doing these two described checks, A and B, in one pass. If the checking algorithm gets through all positions in all directions without reporting "no tie", it reports a tie.
Knowing that you can check a win just by checking in the vicinity of the last piece that was added with running time O(k), I was wondering if there were quicker ways to do an early check for a tie. Doesn't have to be asymptotically quicker. I'm currently keeping the pieces in a two-dimensional array. Is there maybe a data structure that would allow an efficient check? One approach: what is the highest threshold of moves that one can wait the players to make before running any checks for a tie at all?
There are many related questions at Stack Overflow, for example this, but all discussions I could find either only pointed out the obvious tie condition, where the number of moves made is equal to the size of the board (or they checked if the board is full), or handled only the special case where the board is square: m = n. For example this answer claims to do the check for a tie in constant time, but only works when m = n = k. I'm interested in reporting the tie as early as possible and for general m,n and k. Also if the algorithm works for more than two players, that would be neat.
I would reduce the problem of determining a tie to the easier sub-problem:
Can player X still win?
If the answer is 'no' for all players, it is a tie.
To find out whether Player X can win:
fill all blank spaces with virtual 'X'-pieces
are there k 'X'-pieces in a row anywhere?
if there are not --> Player X cannot win. return false.
if there are, find the row of k stones with the least amount of virtual pieces. Count the number of virtual pieces in it.
count the number of moves player X has left, alternating with all other players, until the board is completely full.
if the number of moves is less than the amount of virtual pieces required to win, player X cannot win. return false.
otherwise, player X can still win. return true.
(This algorithm will report a possible win for player X even in cases where the only winning moves for X would have another player win first, but that is ok, since that would not be a tie either)
If, as you said, you can check a win just by checking in the vicinity of the last piece that was added with running time O(k), then I think you can run the above algorithm in O(k * Number_of_empty_spots): Add all virtual X-Piece, note any winning combinations in the vicinity of the added pieces.
The number of empty slots can be large, but as long as there is at least one empty row of size k and player X has still k moves left until the board is filled, you can be sure that player X can still win, so you do not need to run the full check.
This should work with any number of players.
Actually the constant time solution you referenced only works when k = m = n as well. If k is smaller then I don't see any way to adapt the solution to get constant time, basically because there are multiple locations on each row/column/diagonal where a winning consecutive k 0's or 1's may occur.
However, maintaining auxiliary information for each row/column/diagonal can give a speed up. For each row/column/diagonal, you can store the start and end locations for consecutive occurrences of 1's and blanks as possible winning positions for player 1, and similarly store start and end locations of consecutive occurrences of 0's and blanks as possible winning positions for player 0. Note that for a given row/column/diagonal, intervals for player 0 and 1 may overlap if they contain blanks. For each row/column/diagonal, store the intervals for player 1 in sorted order in a self-balancing binary tree (Note you can do this because the intervals are disjoint). Similarly store the intervals for player 0 sorted in a tree. When a player makes a move, find the row/column/diagonals that contain the move location and update the intervals containing the move in the appropriate row column and diagonal trees for the player that did not make the move. For the player that did not make a move, this will split an interval (if it exists) into smaller intervals that you can replace the old interval with and then rebalance the tree. If an interval ever gets to length less than k you can delete it. If a tree ever becomes empty then it is impossible for that player to win in that row/column/diagonal. You can maintain a counter of how many rows/columns/diagonals are impossible to win for each player, and if the counter ever reaches the total number of rows/columns/diagonals for both players then you know you have a tie. The total running time for this is O(log(n/k) + log(m/k)) to check for a tie per move, with O(mn/k) extra space.
You can similarly maintain trees that store consecutive intervals of 1's (without spaces) and update the trees in O(log n + log m) time when a move is made, basically searching for the positions before and after the move in your tree and updating the interval(s) found and merging two intervals if two intervals (before and after) are found. Then you report a win if an interval is ever created/updated and obtains length greater than or equal to k. Similarly for player 0. Total time to check for a win is O(log n + log m) which may be better than O(k) depending on how large k is. Extra space is O(mn).
Let's look at one row (or column or diagonal, it doesn't matter) and count the number of winning lines of length k ("k-line") it's possible to make, at each place in the row, for player X. This solution will keep track of that number over the course of the game, checking fulfillment of the winning condition on each move as well as detecting a tie.
1 2 3... k k k k... 3 2 1
There is one k-line including an X in the leftmost slot, two with the second slot from the left, and so on. If an opposing player, O or otherwise, plays in this row, we can reduce the k-line possibility counts for player X in O(k) time at the time of the move. (The logic for this step should be straightforward after doing an example, needing no other data structure, but any method involving checking each of the k rows of k from will do. Going left to right, only k operations on the counts is needed.) An enemy piece should set the possibility count to -1.
Then, a detectably tied game is one where no cell has a non-zero k-line possibility count for any player. It's easy to check this by keeping track of the index of the first non-zero cell. Maintaining the structure amounts to O(k*players) work on each move. The number of empty slots is less than those filled, for positions that might be tied, so the other answers are good for checking a position in isolation. However, at least for reasonably small numbers of players, this problem is intimately linked with checking the winning condition in the first place, which at minimum you must do, O(k), on every move. Depending on your game engine there may be a better structure that is rich enough to find good moves as well as detect ties. But the possibility counting structure has the nice property that you can check for a win whilst updating it.
If space isn't an issue, I had this idea:
For each player maintain a structure sized (2mn + (1 - k)(m + n) + 2(m - k + 1)(n - k + 1) + 2(sum 1 to (m - k))) where each value represents if one of another player's moves are in one distinct k-sized interval. For example for a 8-8-4 game, one element in the structure could represent row 1, cell 0 to 3; another row 1, cell 1 to 4; etc.
In addition, one variable per player will represent how many elements in their structure are still unset. Only one move is required to set an element, showing that that k-interval can no longer be used to win.
An update of between O(k) and O(4k) time per player seems needed per move. A tie is detected when the number of players exceeds the number of different elements unset.
Using bitsets, the number of bytes needed for each player's structure would be the structure size divided by 8. Notice that when k=m=n, the structure size is 4*k and update time O(4). Less than half a megabyte per player would be needed for a 1000,1000,5 game.
Below is a JavaScript example.
var m = 1000, n = 1000, k = 5, numberOfPlayers = 2
, numberOfHorizontalKIs = m * Math.max(n - k + 1,0)
, numberOfverticalKIs = n * Math.max(m - k + 1,0)
, horizontalVerticalKIArraySize = Math.ceil((numberOfHorizontalKIs + numberOfverticalKIs)/31)
, horizontalAndVerticalKIs = Array(horizontalVerticalKIArraySize)
, numberOfUnsetKIs = horizontalAndVerticalKIs
, upToM = Math.max(0,m - k) // southwest diagonals up to position m
, upToMSum = upToM * (upToM + 1) / 2
, numberOfSouthwestKIs = 2 * upToMSum //sum is multiplied by 2 to account for bottom-right-corner diagonals
+ Math.max(0,n - m + 1) * (m - k + 1)
, diagonalKIArraySize = Math.ceil(2 * numberOfSouthwestKIs/31)
, diagonalKIs = Array(diagonalKIArraySize)
, numberOfUnsetKIs = 2 * numberOfSouthwestKIs + numberOfHorizontalKIs + numberOfverticalKIs
function checkTie(move){
var row = move[0], column = move[1]
//horizontal and vertical
for (var rotate=0; rotate<2; rotate++){
var offset = Math.max(k - n + column, 0)
column -= offset
var index = rotate * numberOfHorizontalKIs + (n - k + 1) * row + column
, count = 0
while (column >= 0 && count < k - offset){
var KIArrayIndex = Math.floor(index / 31)
, bitToSet = 1 << index % 31
if (!(horizontalAndVerticalKIs[KIArrayIndex] & bitToSet)){
horizontalAndVerticalKIs[KIArrayIndex] |= bitToSet
numberOfUnsetKIs--
}
index--
column--
count++
}
//rotate board to log vertical KIs
var mTmp = m
m = n
n = mTmp
row = move[1]
column = move[0]
count = 0
}
//rotate board back
mTmp = m
m = n
n = mTmp
// diagonals
for (var rotate=0; rotate<2; rotate++){
var diagonalTopColumn = column + row
if (diagonalTopColumn < k - 1 || diagonalTopColumn >= n + m - k){
continue
} else {
var offset = Math.max(k - m + row, 0)
row -= offset
column += offset
var dBeforeM = Math.min (diagonalTopColumn - k + 1,m - k)
, dAfterM = n + m - k - diagonalTopColumn
, index = dBeforeM * (dBeforeM + 1) / 2
+ (m - k + 1) * Math.max (Math.min(diagonalTopColumn,n) - m + 1,0)
+ (diagonalTopColumn < n ? 0 : upToMSum - dAfterM * (dAfterM + 1) / 2)
+ (diagonalTopColumn < n ? row : n - 1 - column)
+ rotate * numberOfSouthwestKIs
, count = 0
while (row >= 0 && column < n && count < k - offset){
var KIArrayIndex = Math.floor(index / 31)
, bitToSet = 1 << index % 31
if (!(diagonalKIs[KIArrayIndex] & bitToSet)){
diagonalKIs[KIArrayIndex] |= bitToSet
numberOfUnsetKIs--
}
index--
row--
column++
count++
}
}
//mirror board
column = n - 1 - column
}
if (numberOfUnsetKIs < 1){
return "This player cannot win."
} else {
return "No tie."
}
}

Three egg proble​m

I was just reading The Two Egg Problem:
The Two Egg Problem
You are given two eggs, and access to a 100-storey building. Both eggs are identical. The aim is to find out the highest floor from which an egg will not break when dropped out of a window from that floor. If an egg is dropped and does not break, it is undamaged and can be dropped again. However, once an egg is broken, that’s it for that egg.
If an egg breaks when dropped from floor n, then it would also have broken from any floor above that. If an egg survives a fall, then it will survive any fall shorter than that.
The question is: What strategy should you adopt to minimize the number egg drops it takes to find the solution?. (And what is the worst case for the number of drops it will take?)
I was following along until the "Look see I can do three" section. The author states that after the first egg breaks it degrades into the 2-egg problem and can be solved recursively.
That's great, but wouldn't we want to choose larger step-sizes when using 3 eggs instead of 2 (for the first egg)? From which floor do we throw the first egg?
With 1 egg, we have to start at floor 1.
With 2 eggs, we solve for n(n+1)/2=k and round up, where n is the starting floor, and k is the number of floors.
With 3... I'm having trouble coming up with a formula.
Thinking about this a bit more, with 2 eggs, the maximum number of drops is equal to the floor number that we drop our first egg from. For example, with 2 eggs and 100 floors, the solution is 14, which means we drop the first egg from floor 14, and if it breaks, we have to drop up to 13 more times, for floors 1-13.
With 3 eggs, the solution is 9 (as shown in the chart). But we wouldn't want to throw the first egg at floor 9, we can throw it higher, because we don't have to iterate by 1s in-between.
If we throw from floor 14 again, and it breaks, then we recurse. n(n+1)/2=k where k is now 13... but that gives us 4.815, if we ceil and that and add our previous drop we get 6, which is lower than the actual solution, so something here is wrong...
If we throw from floor 14 again, and it breaks, then we recurse. n(n+1)/2=k where k is now 13... but that gives us 4.815, if we ceil and that and add our previous drop we get 6, which is lower than the actual solution, so something here is wrong...
What if it doesn't break? Then you have a three-egg problem with 86 floors, which takes maybe one drop less to solve than the 100-floor problem.
Say you drop the first egg from the 50th floor. If it breaks, you have a two-egg problem with 49 floors, which takes up to 10 additional drops. So that would give you a worst-case of 11 drops (since if it doesn't break, the 50-floor three-egg problem takes at most 7 additional drops).
If you choose the 37th floor for the first drop, if it breaks, you have a 36-floor two-egg problem, needing up to 8 additional drops. If it doesn't break, you have a 63-floor three-egg problem left. You want to solve that problem with at most 8 drops, so if the next drop breaks the egg, the remaining two-egg problem should be solvable in at most 7 drops, thus the highest floor you can choose for the second drop is 37 + 28 + 1 = 66, since 28 floors is the highest you can solve with at most 7 drops and two eggs. If the egg doesn't break, you have a 34-floor three-egg problem with 7 drops left. The highest you can certainly solve with the remaining 6 drops if the egg breaks is 21 (6*7/2), so you can choose floor 66 + 21 + 1 = 88. If the egg doesn't break, you have 12 floors left with 6 drops, which is already doable with only two eggs.
Systematically, the highest number of floors you can certainly solve with d drops and e eggs is
/ 1, if d == 1
F(e,d) = | d, if e == 1
\ F(e-1,d-1) + 1 + F(e,d-1), if e > 1 and d > 1
If you have only one drop, you have no choice but to choose the lowest floor of which you do not yet know that the egg doesn't break. If that breaks it, and you tried a higher floor, you don't know the first floor to break the egg.
If you have only one egg, you have to check every floor in order until the egg breaks or you run out of drops.
Otherwise, if the first drop is from a floor higher than F(e-1,d-1) + 1, you possibly can't find the first breaking floor if the egg breaks. If the first drop is from a lower floor, you can't reach as high with d-1 drops if the egg doesn't break, so the first drop should be from floor F(e-1,d-1) + 1. If it breaks, you can solve with the remaining e-1 eggs and d-1 drops by assumption. If not, you can solve for the next F(e,d-1) floors with the remaining drops and eggs.
Conversely, to find how many drops you may need for f floors with e eggs, you have to find
D(e,f) = min { d | F(e,d) >= f }
You can find that by calculating the F(e,d) matrix, or you can use dynamic programming:
If you choose floor s for the first drop, if the egg breaks, you need up to D(e-1,s-1) drops to determine the floor. If the egg doesn't break, you need up to D(e,f-s) drops to determine the floor.
So the worst case for choosing floor s for the first drop is
WC(s,e,f) = 1 + max { D(e-1,s-1), D(e,f-s) }
and the best of the worst case is
D(e,f) = minimum { WC(s,e,f) | 1 <= s <= f }
(where of course D(e,0) = 0).
This problem can be solved with following 3 approaches (that I know) :
Dynamic Programming
Solution using Binary Search Tree
Solution by obtaining the direct mathematical formula for maximum number of floors that can be tested or covered with given number of eggs and given number of drops
Let me first define some symbols as follows:
e = number of eggs
f = number of floors in building
n = number of egg drops
Fmax(e, n) = maximum number of floors that can be tested with e eggs and n drops
The crux for dynamic programming approach lies in following recursive formula for Fmax:
Fmax(e, n) = 1 + Fmax(e-1, n-1) + fmax(e, n-1)
And the crux for obtaining the direct mathematical formula for Fmax lies in following recursive formula for Fmax:
Fmax(e, n) = { ∑Fmax(e-1,i) for i = 1 to n } - Fmax(e-1, n) + n
Alternative solution using Binary Search Tree (BST) is also possible for this problem. In order to facilitate our analysis, let us draw BST with slight modifications as follows:
1. If egg breaks then child node is drawn on left down side
2. If egg does not break then child node is drawn straight down side
If we draw BST with above kind of representation then width of the BST (i.e. number of vertical columns in BST) represents the number of eggs.
Any BST with f number of nodes, drawn with above kind of representation and subjected to the constraint width of BST <= e (number of eggs) is a solution but it may not be the optimal solution.
Hence obtaining the optimal solution is equivalent to obtaining the arrangement of nodes in BST with minimum height subjected to the constraint: width of BST <= e
For more details about all the above 3 approaches, check my blog at: 3 approaches for solving generalized egg drop problem
This is the same answer that I provided at Egg Drop Printing Solutions. I am providing it here for anyone who wants to see the whole decision tree and reasoning laid out.
# This uses dynamic programming to find the basic information.
def optimal_solution(floors, eggs):
# dp[drops][eggs] = max_floors
dp = []
# With no drops, we can do no floors
dp.append([0 for x in range(eggs+1)])
# With one drop and any eggs, we can do 1 floor
one_drop = [1 for _ in range(eggs+1)]
one_drop[0] = 0 # Except no eggs is still no floors
dp.append(one_drop)
# Keep adding drops until we have our answer
# Note, in python array element -1 is shorthand for the end of the array.
while dp[-1][eggs] < floors:
# 0 floors for 0 eggs. 1 more for one egg
next_drop = [0, dp[-1][1] + 1]
for i in range(2, eggs+1): # Python for 2..eggs
# The best we can do is drop at floor dp[-1][i-1].
# If the egg breaks, we can find the answer using that solution.
# If the egg holds, we can find another dp[-1][i] floors.
next_drop.append(dp[-1][i] + dp[-1][i-1])
dp.append(next_drop)
return dp
# This turns that optimal solution into a decision tree.
def dp_to_decision_tree(dp, floors, start_floor=None, eggs=None, drops=None):
# Figure out defaults if needed.
if start_floor is None:
start_floor = 0
if drops is None:
drops = len(dp) - 1
if eggs is None:
eggs = len(dp[0]) - 1
# Are we done?
if floors == start_floor:
return start_floor
elif dp[drops][eggs] < floors - start_floor:
return None
# Do we need all of our drops?
while floors - start_floor < dp[drops-1][eggs]:
drops -= 1
drop_at = start_floor + dp[drops-1][eggs-1]
if eggs == 1:
drop_at = start_floor + 1
if floors < drop_at:
drop_at = floors
return [
drop_at,
dp_to_decision_tree(dp, floors, drop_at, eggs, drops-1),
dp_to_decision_tree(dp, drop_at-1, start_floor, eggs-1, drops-1),
{'eggs': eggs, 'floor_range': (start_floor, floors)}
]
# This prints the decision tree in a human readable format.
def print_decision_tree(tree, indent="", label="start"):
if tree is None:
print(f"{indent}{label}: ?")
elif isinstance(tree, int):
print(f"{indent}{label}: {tree} found")
else:
print(f"{indent}{label}: {tree[0]} {tree[3]}")
print_decision_tree(tree[1], indent + " ", label="held")
print_decision_tree(tree[2], indent + " ", label="broke")
# And this calls the previous functions.
def print_optimal_decisions(floors, eggs):
print_decision_tree(
dp_to_decision_tree(
optimal_solution(floors, eggs), floors))
# And now we can try it.
print_optimal_decisions(36, 3)
You can use simple dynamic programming solution.
n - number of floors.
k - number of eggs.
D[n,k] = you answer (The number of minimum throws).
D[j,1] = n-1 for each 1 <= j <= n.
The main idea for calculate D[n,k] for k>1:
D[n,k] = maximum 1 <= j <= n-1 { maximum{ D[j,k-1]+1, D[n-j,k]+1 }.
The blog here explains the mathematical approach for 3 eggs with 1000 floors:
https://sankalpiitr.wordpress.com/2012/03/02/the-2-eggs-problem-extended-to-3-eggs/
According to this blog formula would be
P = N + ( N * ( N + 1 ) * ( N – 1 ) ) /6
where P is the number of floors and N is the minimum drops required.
So solving for P=100, we get N as 8.2

Algorithm: finding the biggest element of a list

The catch: only comparisons between elements of the list is allowed. For example, suppose we have 1,000,000 chess players, and we are assigned the task of finding the best chess player in the group. We can play one chess player against any other chess player. Now, we want to minimize the maximum number of games any player plays.
If player A beats player B, and B beats C, we can assume that A is better than C. What is the smallest n such that no player plays more than n games?
#Carl: This is not homework; it's actually a subproblem of a larger problem from SPOJ.
I would wager a guess that the answer is the binary log of the number of people.
You set up a binary tree as a tournament ladder. This means the most games anyone plays is the height of the tree. The height of the binary tree would be log n
How do I find the biggest element of a list
If the list is ordered, then the biggest element is the first (or last) element of the list.
If the list is not ordered then:
Element biggest = list.get(0);
for (Element e : list) {
if (e.compareWith(biggest) > 0) {
biggest = e;
}
}
For example, suppose we have 1,000,000 chess players, and we are assigned the task of finding the best chess player in the group. Now, we want to minimize the maximum number of games any player plays.
With the new constraint of the last sentence ...
Answer #1: zero games played. Compare the chess player's rankings and the one with the best ranking is the objectively best player ... according to the ranking.
Answer #2: at most ceiling(log2(nos_players)) games played per player. A "knock out" / elimination tournament eliminates half the players in each round, so the number of rounds and hence the maximum number of games played by any one player is ceiling(log2(nos_players)).
The corresponding algorithm is trivially:
List players = ...
while (players.size() > 1) {
List winners = new ArrayList();
Iterator it = players.iterator();
while (it.hasNext()) {
Player p1 = it.next();
if (it.hasNext()) {
Player p2 = it.next();
int result = p1.compareTo(p2);
if (result < 0) {
winners.add(p2);
} else if (result > 0) {
winners.add(p1);
} else {
throw new Exception("draws are impossible in chess");
}
} else {
winners.add(p1); // bye
}
}
players = winners;
}
(Aside: if you also have a predetermined ranking for the players and the number of players N is at least 2 less than ceiling(log2(N)), you can arrange that the best 2 players get a bye in one round. If the best 2 players meet in the final, then everyone will have played less than ceiling(log2(N)) games ... which is an improvement on the solution where the byes are allocated randomly.)
In reality, answer #2 does not work for the game of chess because it does not take account of the fact that a significant percentage of real chess games are draws; i.e. neither player wins. Indeed, the fact that player A beat player B in one game does not mean A is a better player than B. To determine who is the better of any two players they need to play a number of games and tally the wins and losses. In short, the notion that there is a "better than" relation for chess players is TOTALLY unrealistic.
Not withstanding the points above, knock-out is NOT a practical way to organize a chess tournament. Everyone will be camped out on the tournament organizer's desk complaining about having to play games against players much better (or worse) than themselves.
The way a real chess (or similar) tournament works is that you decide on the number of rounds you want to play first. Then in a "round-robin" tournament, you select the top N players by ranking. and arrange that each player plays each other player. The player with the best win / draw score is the winner, and in the event of a tie you use (say) "sum of opponents scores" as a tie breaker. There are other styles of tournament as well that cater for more players / less rounds.
As far as I know there is no algorithm to solve your problem without any additional outside information to rank the players (such as seeding). If you could seed the players appropriately you can find the best player in less rounds than the worst case suggested by J. Wong.
Example of the results of 2 rounds of 10 players: A is the best, ceil(log 10) = 4
A > B; C > D; E > F; G > H; I > J
A > C; B > E; F > G; D > I
Instead of building an Abstract Data Structure such as a binary tree and resolving a tournament, you could re-interpret your goal in a different light:
Eliminate all the elements on the list that are not the largest
You will find that doing this may be much more algorithmically expedient than building a tree and seeding a "tournament".
I can demonstrate that eliminating all elements on a list that are not the largest can be done with a worst-case scenario of log n calls/comparisons per element.
Work on a copy of your original list if possible.
Pair off consecutive elements and remove from the list the lower-valued of the two. Ignore the unpaired element, if there is one.
This can be done by iterating from 0 <= i < int(n/2) and comparing indices 2i and 2i+1.
i.e., for n=7, int(n/2) = 3, i = 0,1,2; compare indices 0 and 1, 2 and 3, 4 and 5.
There should be a total of int(n/2) indices eliminated. Subtract that count from n. Then, repeat 1 until there is only one index remaining. This will be your largest.
Here is an implementation in Ruby:
def find_largest(list)
n = list.size
working_list = list.clone()
while n > 1
temp_list = Array.new()
for i in (0...n/2) # remember to cast n/2 to integer if not automatic
if working_list[2*i] > working_list[2*i+1]
new_list.push(working_list[2*i])
else
new_list.push(working_list[2*i+1])
end
end
working_list = temp_list
n -= n/2 # remember to cast n/2 to integer if not automatic
end
return working_list[0]
end

Resources