Cannot find a solution for a budget expenditure maximisation problem - algorithm

I am trying to solve a DP problem which consists on the following:
Let's say that we are a town hall with a budget B and have a set of projects P, each one with a cost p_i to build. Since the town hall's budget leftovers at the end of the year will not carry to the next one, they want to maximize the expenditure as much as possible.
Due to this, they want to find the maximum possible cost they can have by approving projects from P, without surpassing the maximum budget B.
So far, I have established the following recurrence relation/equation:
maxCost (B, i) =
if (B >= p_i)
// Since the budget allows it, choose the maximum between approving this
// project and evaluating the previous one with a smaller budget, or not
// approving it and evaluating the previous one with the same budget
max{maxCost(B-p_i, i - 1) + p_i, maxCost(B, i - 1)}
else
// Since the budget does not allow approving the current project, check the
// previous one with the same budget
maxCost(B, i - 1)
Additionally, maxCost (B, i) = 0 if i > length(P) || j < 1 (or 0, if 0-indexed)
The memoization structure can be a cost array C of size B (ignoring memory issues if B is enormous, or non-integer), where we always fill it from the right (we will always consult Cfor the evaluation of the previous project, for values smaller than the current budget, and write onto C[B]). The initial values for C will all be 0.
The problem appears when writing the algorithm itself, which translates to replacing maxCost(B, i) with C[B], keeping and updating B as a variable depending on which choice is made (approve or pass onto the next project), and iterating over the projects from the first one to the last one.
If the algorithm is done like that, any mock example will show that the solution is not optimal (it behaves more like a greedy algorithm), since the budget is subtracted unconditionally from the start and later projects will not be able to get approved, even if they were better choices.
For example, for:
B = 50
P = [10, 20, 40]
C = [0, 0, ... , 0] (size 50)
The algorithm will choose to approve the first and second projects, and the third project will not get approved since B(30)<40, but the optimal solution would be approving the first and third projects.
Is there something missing from the equations, or is the implementation for this case special? I have been unable to find a similar base problem for this one, so I couldn't find similar solutions.

Related

Football Guaranteed Relegation/Promotion Algorithm

I'm wondering if there is a way to speed up the calculation of guaranteed promotion in football (soccer) for a given football league table. It seems like there is a lot of structure to the problem so the exhaustive solution is perhaps slower than necessary.
In the problem there is a schedule (a list of pairs of teams) that will play each other in the future and a table (map) of points each team has earned in games in the past. I've included a sample real life points table below. Each future game can be won, lost or tied and teams earn 3 points for a win and 1 for a tie. Points (Pts) is what ultimately matters for promotion and num_of_promoted_teams (positive integer, usually around 1-3) are promoted at the end of each season.
The problem is to determine which (if any) teams are currently guaranteed promotion. Where guaranteed promotion means that no matter the outcome of the final games the team must end up promoted.
def promoted(num_of_promoted_teams, table, schedule):
return guaranteed_promotions
I've been thinking about using depth first search (of the future game results) to eliminate teams which would lower the average but not the worst case performance. This certainly help early in the season, but the problem could become large in mid-season before shrinking again near the end. It seems like there might be a better way.
A constraint solver should be fast enough in practice thanks to clever pruning algorithms, and hard to screw up. Here’s some sample code with the OR-Tools CP-SAT solver.
from ortools.sat.python import cp_model
def promoted(num_promoted_teams, table, schedule):
for candidate in table.keys():
model = cp_model.CpModel()
final_table = table.copy()
for home, away in schedule:
home_win = model.NewBoolVar("")
draw = model.NewBoolVar("")
away_win = model.NewBoolVar("")
model.AddBoolOr([home_win, draw, away_win])
model.AddBoolOr([home_win.Not(), draw.Not()])
model.AddBoolOr([home_win.Not(), away_win.Not()])
model.AddBoolOr([draw.Not(), away_win.Not()])
final_table[home] += 3 * home_win + draw
final_table[away] += draw + 3 * away_win
candidate_points = final_table[candidate]
num_not_behind = 0
for team, team_points in final_table.items():
if team == candidate:
continue
is_behind = model.NewBoolVar("")
model.Add(team_points < candidate_points).OnlyEnforceIf(is_behind)
model.Add(candidate_points <= team_points).OnlyEnforceIf(is_behind.Not())
num_not_behind += is_behind.Not()
model.Add(num_promoted_teams <= num_not_behind)
solver = cp_model.CpSolver()
status = solver.Solve(model)
if status == cp_model.INFEASIBLE:
yield candidate
print(*promoted(2, {"A": 10, "B": 8, "C": 8}, [("B", "C")]))
Here’s an alternative solution that is less extensible and probably slower in exchange for being self-contained and predictable.
This solution consists of an algorithm to test whether a particular team can finish behind a particular set of other teams (assuming unfavorable tie-breaking), wrapped in a loop over pairs consisting of a top-k team ℓ and a set of k teams W that might or might not finish ahead of ℓ (where k is the number of promoted teams).
If there were no draws, then we could use bipartite matching. Mark ℓ as having lost its remaining matches and mark W as having won their matches against teams not in W. On one side of the bipartite graph, there are nodes corresponding to matches between members of W. On the other side, there are zero or more nodes for each team in W, corresponding to the number of matches that that team must win to pull ahead of ℓ. If there is a matching that completely matches the latter side, then W can finish collectively in front of ℓ, and ℓ is not guaranteed promotion.
This could be extended easily if wins were 2 points instead of 3, but alas, 3 points causes the problem not to be convex, and we’re going to need some branching. The simplest branching method depends on the observation that it’s better for two teams to each win once and lose once against each other than draw twice. Hence, loop over all subsets of at most k choose 2 pairs of teams and run the algorithm above after marking each pair in the subset as having drawn once.
(I could propose improvements, but k is small, computers are cheap, programmers are expensive, and sports fans are relentless.)

Solving a travelling salesman problem to maximize gain in minimum time

Team
I need suggestions on how to solve the below problem.
There are n places (for example say 10 places). Time taken from any one place to the other is known. On reaching a particular place a known reward is given in the form of rupees (ex. if I travel from place 1 to place 2, I get 100 rupees. Travelling from place 2 to place 3 will fetch me 50 rupees etc...). Also, sometimes a particular place is unavailable to travel to which changes with time. At all time instances, whatever places can be traveled to is known, reward fetched from each place is known and the time taken to travel from one place to other is known. This is an ongoing process, meaning after you reach place A and earn 100 rupees, you travelled to place B and fetch 100 Rs. Then it is possible that place A can again fetch you rupees say 50 if you travel from B to A again.
Problem statement is:
A path should be followed with time ( A to B, B to C, C to B, B to A etc...) so that I always have maximum rupees in a given time. Thus at the end of 1 month, I should have followed a path that fetches me the maximum amount among all possibilities available.
We already know that in the traveling salesman problem it takes O(N!) to calculate the best way for the month if there are no changes. Because of the unknown changes that can happen, the best way is to use a greedy algorithm such that every time you come to new place, you calculate where you get the most R's in the least amount of time. It will take O(N*k) where k is the amount of time that you move between places in a month.
I'm not sure how this problem is related to travelling salesman -- I understood the latter as having the restriction of at least visiting all the places once.
Assuming we have all of the time instances and their related information ahead of our calculation, if we work backwards from each place we imagine ending at, the choices we have for the previous location visited dictate the possible times it took to get to the last place and the possible earning we could have gotten. Clearly from those choices we would choose the best reward among them because it's our last choice. Apply this idea recursively from there, until we reach the start of the month. If we run this recursion from each possible ending place, we can reuse states we've seen before; for example if we reached place A at time T as one of the options when calculating backwards from B, and then we reach A again at time T when calculating a path that started at C, we can reuse the record for the first state. The search space would be O(N*T) but practically would vary with the input.
Something like this? (Assumes we cannot wait in any one place. Otherwise, the solution could be better coded bottom-up where we can try all place + time states.) Return the best of running f with the same memo map on all possible ending states.
get_travel_time(place_a, place_b):
# returns travel time from place a to place b
get_neighbours(place):
# returns places from which we can travel to place
get_reward(place, time):
# returns the reward awarded at place place at time time
f(place, time, memo={}):
if time == 0:
return 0
key = (place, time)
if key in memo:
return memo[key]
current_reward = get_reward(place, time)
best = -Infinity
for neighbour in get_neighbours(place):
previous_time = time - get_travel_time(neighbour, place)
if previous_time >= 0:
best = max(best, current_reward + f(neighbour, previous_time, memo))
memo[key] = best
return memo[key]

Build a ranking from a series of transitive relationships that can be noisy, inconsistent, or incomplete

I have a list of ~1000 different assets that I would like to rank by value for a game that I am making.
Players are given the opportunity to select one of two baskets of assets. For instance, they might be asked if they would rather have A + B or C.
From a huge list of basket preferences, I wish to rank the assets by perceived value.
Here is some example input, where accordingly players have said:
A > B
A + A > B + C
C > B
I.e. they would rather have an A than a B. they would rather have 2 As than a B and a C. Etc.
From this input, I believe the most likely value ranking is:
A > C > B
What class of algorithm should I use to attack this problem?
Sometimes the list of preferences will be contradictory (some players think A > B, others might say B > A). If I have a separate measure of player skill level, how can I leverage this to get a more accurate ranking?
I also need to be able to handle the case where there are "islands" in the relationships between baskets. For example:
A > B
C > D
I.e. you cannot say if A <> C.
This seems to me like an optimization problem similar to various packing algorithms. Is this ranking problem NP-hard?
It actually sounds more like graph theory to me.
Suppose you would have a digraph with A -> B indicating that A < B. Then you could
Run a minimum feedback set algorithm for the ordering (which, in the absence of contradictions, is simply Topological Sort).
Run DFS for the "islands" you mention.
So the question is how to build such a graph. I have a hunch (unfortunately not more than that yet), that the only multiple-variable rules that matter are of the form:
A_1 + A1 + ... + A_i < A_{i + 1}
And they should be simplified to
A_j < A_{i + 1}, j = 1, .., i.
(Asides from that, the only use of RHS multiple value rules would be for transitive deductions (according to my hunch), that is
A < B + C and B + C < D implies A < D. But this can be dealt with using dummy variables for the sums.)
Perhaps you should see if you can verify or contradict this.
What you are trying to do is in general impossible under reasonable assumptions by Arrow's Theorem.
A better approach would be to ask the players to score rather than rank; e.g., ask them, "How many zlotys would you pay for A? For B? For C?", then average the scores for each item by the players who answered. So if only players 2, 5, and 11 answered with their estimates of the value of item A, you would add their answers and divide by 3. You could also give more weight to the answers of more experienced players.
If you still want to use baskets of assets, you can probably use linear algebra to disentangle scores for A and B based on scores for A+B and A-B, say.
If you want to make do with ranking data (e.g., if that's all you have and you can't ask any more questions), then Ami's approach is probably the best you can do.

Find minimum time required to collect C gifts from A machines given access to B bags?

I have an interview lined up for the next week and am practicing problems for the same. I came across this question, could you please help me with how to solve it?
There are A gift vending machines and you have B pouches to
collect the gifts in. Each gift vending machine has unlimited gifts.
Each machine starts dropping the gift at time Si second and keeps
dropping a gift every Ii seconds. You can arrange the pouches in
any way you like but once they are set below one machine, you cannot
move it to another machine. You need to collect a minimum of C gifts
in minimum time.
Input
The first line contains the integers A, B, C.
The second line contains integers, Si. ( separated with a space),
I goes from 1 to A.
The third line contains integers Ii ( separated with a space), I
goes from 1 to A.
Output
An integer which gives the minimum time calculated.
The method I thought was pretty inefficient. I thought that we can have all subsets with their cardinal number equal to B and then choose the one which gives the minimum time.
This approach seems to be brute force, so I'm looking for another alternative approach.
Can any of you please help me with it?
First of all write a function
int max_num_gifts(A,B,t,S[],l[])
which calculates the maximum number of gifts that you can collect with B bags in t time. This can be done in O(A) time: given S[] and l[] the number of gifts the machine A[i] drops is (t-S[i])/l[i]+1. Then get the top B machines which drop the most gifts. See How to find the kth largest element in an unsorted array of length n in O(n)? on how to do the selection in O(n) time.
Then you can just loop through the times t=1,2,... until max_num_gifts >= C. Or better still, you can use a binary search to find such a t: Start with t=1, then check t=2, t=4, t=8 etc until you get too big a number, then binary search for the required t.
The overall time complexity should be O(A* lg(T_min)).
One possible approach can be as follows:
Simulate each vending machine simultaneously by stepping through the events. For each machine calculate the time after which it will drop the gift and step through that time.
Also, maintain a priority queue of all the machines, ordered by the number of gifts dropped till present.
Then when the sum of gifts top B machines equals C, that is the minimum time T_min.
Order of complexity: O( T_min * A * logA )
First you have to sort the starting times S_i of the vending machines in A in ascending order. With this prerequisite, I think, the problem get's clearer if you write it as an equation:
Where \Theta is the Heaviside step function and the special braces denote the floor function.
A small python script to find T when gamma exceeds C could look like
import numpy as np
A=10
B=3
C=10
S=np.sort(np.random.randint(100, size=A))
gamma=0
t=1
while gamma<C:
for i in range(1, B):
gamma=(1 if t-S[i] > 0 else 0) * np.floor(t/2)
t = t+1
print "minimal t: %d" % t
I did not read the input values A, B and C from a file and generated the starting times S randomly between 0 and 100. You also would need to check if B<A.
For simplicity, let's make the following assumptions (we can relax these later and work out the edge cases):
Each machine produces a 'fractional gift' of 1/l[i] at every time step.
All S[i] are different, i.e., no two machines start dispensing at the same time.
Let's consider the two border cases first:
If we have more pouches than machines, we don't have to choose between them; at every time step S[i], we add a pouch to machine i, and keep collecting all gifts until we have a total of C.
If |B| = 1 (only one pouch), we add the pouch at the minimum S[i]. We keep stepping through time until either we reach C, or until another machine would have led to a higher total at that time, then switch the pouch due to 20-20 hindsight. Note that mathematically, the switch point is the intersection of the two lines that cross the x-axis at S[i], and have slope 1/l[i]. We will come back to that later.
Above considerations lead to the following general (naive) algorithm, in pseudocode.
def min_time_to_reach_c(S,l):
def function yield(m, t) = max(0, t-S[m])/l[m]
Active = {} # The currently best set of machines that we attach our pouches to
Inactive = {1,..,A} # The other machines
t = 0 # time step
sum = 0 # total gifts collected so far
gift_rate = 0 # gifts we receive at every time step
while sum < C:
if |Active| < B and t == S[m’] for some m’ in InActive:
# we have not used up all our pouches yet
Active += {m’}
InActive -= {m’}
gift_rate += l[m’]
else if yield(m’,t) > yield(m,t) for some m’ in InActive, m in Active: (*)
# for all successive time steps, m’ will be preferable to m
Active += {m’} - {m}
InActive -= {m’}
sum += yield(m’,t) - yield(m,t)
gift_rate += l[m’] - l[m]
sum += gift_rate
t += 1
return t
The complexity of this naive algorithm is at most O( t_min * A), if in each step (*) we determine the 'crossing' by finding the maximum (minimum) yields of the (In)Active sets by one linear pass each.
If A < t_min, we can actually do better by thinking about the geometry - it can be viewed as a sweep line algorithm. In fact, it is not necessary to visit each time point, we only need to look at the 'interesting' points where a machine starts or lines cross. In a preprocessing step, we calculate all time steps with intersections (if a line does not cross another one and is dominated, it can be dropped right away). At each such point, we can update our active set and the yield rate as above. We also calculate the distance to reach C at the current rate; if this occurs before the next S[i] or crossing, we are done.
The complexity of the sweep line algorithm is O(A^2) for the preprocessing step, plus another O(A^2) for the at most 2A update steps (or better, O(A log A) if we use a priority queue to keep track of the relative order of lines). Note that this is independent of t_min.
I think an interesting follow-up question would be: What would be the best strategy if you didn't know the S[i] and l[i] in advance?

scheduling n people with given time of travel

this is a puzzle but i think it could be a classical algorithm which i am unaware of :
There are n people at the bottom of a mountain, and everyone wants to go up, then down the mountain. Person i takes u[i] time to climb this mountain, and d[i] time to descend it.
However, at same given time atmost 1 person can climb , and .atmost 1 person can descend the mountain. Find the least time to travel up and back down the mountain.
Update 1 :
well i tried with few examples and found that it's not reducible to sorting , or getting the fastest climbers first or vice versa . I think to get optimal solution we may have to try out all possible solutions , so seems to be NP complete.
My initial guess: (WRONG)
The solution i thought is greedy : sort n people by start time in ascending order. Then up jth person up and kth down where u[j]<= d[k] and d[k] is minimum from all k persons on top of mountain. I am not able to prove correctness of this .
Any other idea how to approach ?
A hint would suffice.
Try to think in the following manner: if the people are not sorted in ascending order of time it takes them to climb the mountain than what happens if you find a pair of adjacent people that are not in the correct order(i.e. first one climbs longer than second one) and swap them. Is it possible that the total time increases?
I think it is incorrect. Consider
u = [2,3]
d = [1,3]
Your algorithm gives ordering 0,1 whereas it should be 1,0.
I would suggest another greedy approach:
Create ordering list and add first person.
For current ordering keep track of two values:
mU - time of last person on the mountain - time of the end
mD - time of earliest time of first descending
From people who are not ordered choose the one which minimises abs(mD - d) and abs(mU - u). Then if abs(mD - d) < abs(mU - u) he should go at the beginning of ordering. Otherwise he goes at the end.
Some tweak may still be needed here, but this approach should minimise losses from cases like the one given in the example.
The following solution will only work with n <= 24.
This solution will require dynamic programming and bit-mask technique knowledge to be understood.
Observation: we can easily observe that the optimal total climb up time is fixed, which is equalled to the total climb up time of n people.
For the base case, if n = 1, the solution is obvious.
For n = 2, the solution is simple, just scan through all 4 possibilities and calculate the minimum down time.
For n = 3, we can see that this case will be equal to the case when one person climb up first, followed by two.
And the two person minimum down time can be easily pre-calculated. More important, this two person then can be treated as one person with up time is the total up time of the two, and down time is the minimum down time.
Storing all result for minimum down time for cases from n = 0 to n = 3 in array called 'dp', using bit-mask technique, we represent the state for 3 person as index 3 = 111b, so the result for case n = 3 will be:
for(int i = 0; i < 3; i++){
dp[3] = min(dp[(1<<i)] + dp[3^(1<<i)],dp[3]);
}
For n = 4... 24, the solution will be similar to case n = 3.
Note: The actual formula is not just simple as the code for case n = 3(and it requires similar approach to solve as case n = 2), but will be very similar,
Your approach looks sensible, but it may be over-simplified, could you describe it more precisely here?
From your description, I can't make out whether you are sorting or something else; these are the heuristics that I figured you are using:
Get the fastest climbers first, so the start using the Down path
asap.
Ensure there is always people at the top of the mountain, so
when the Down path becomes available, a person starts descending
immediately.The way you do that is to select first those people who
climb fast and descend slowly.
What if the fastest climber is also the fastest descender? That would leave the Down path idle until the second climber gets to the top, how does your algorithm ensures that this the best order?. I'm not sure that the problem reduces to a Sorting problem, it looks more like a knapsack or scheduling type.

Resources