Urgent Help Needed: Kattis "Workout for a Dumbbell" - Wrong Answer and Failing Sample Case (Python)
Posted by Excellent_Dingo_7109@reddit | learnprogramming | View on Reddit | 9 comments
Hi r/learnprogramming,
I’m struggling with the Kattis problem "Workout for a Dumbbell" (https://open.kattis.com/problems/workout) and keep getting Wrong Answer (WA) verdicts. Worse, my code and a revised version I worked on don’t even pass the sample test case (outputting 100
). A book I’m using calls this a "gym simulation" problem and suggests using 1D arrays to simulate time quickly, but I’m clearly misinterpreting something, especially the two-way waiting rule ("Jim’s usage sometimes results in the other people having to wait as well"). I’d really appreciate your help figuring out what’s wrong or how to approach this correctly!
Problem Description
Jim schedules workouts on 10 machines, using each exactly three times. He has fixed usage and recovery times per machine. Another person uses each machine with their own usage time, recovery time, and first-use time, following a periodic schedule. Key rules:
- Jim’s Schedule: Starts at time 0 (ready for machine 1), uses a machine for
jim_use
time, recovers forjim_recovery
(doesn’t occupy the machine). - Other Person’s Schedule: Starts at
machine_first_use
, uses formachine_use
, recovers formachine_recovery
, repeating everycycle = machine_use + machine_recovery
. - Politeness Rule: If Jim and the other person want to start at the same time (
current_time == usage_start
), Jim waits untilusage_end
. - Two-Way Waiting: Jim’s usage can delay the other person’s next usage until Jim finishes (
jim_end
). - Output: Time when Jim finishes his third use of machine 10 (end of usage, not recovery).
- Constraints: Usage and recovery times are positive ≤ 5,000,000;
machine_first_use
satisfies |t| ≤ 5,000,000.
Input
- Line 1: 20 integers (
jim_use1, jim_recovery1, ..., jim_use10, jim_recovery10
). - Next 10 lines: 3 integers per machine (
machine_use, machine_recovery, machine_first_use
).
Sample Input/Output
Input:
5 5 3 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
2 2 1
8 3 0
1 1 0
1 1 0
1 1 0
1 1 0
1 1 0
1 1 0
1 1 0
1 1 0
Output: 100
My Original Code
My approach used a fixed order (machines 1–10, three times), calculating wait times with modulo operations and an offset to adjust the other person’s schedule. It doesn’t produce 100
for the sample input and gets WA on Kattis, likely due to misinterpreting the two-way waiting rule.
def workout(jim_use, jim_recovery, machine_use, machine_recovery, machine_first_use, machine_offset, current_time):
time_taken = 0
wait_time = 0
one_cycle = machine_recovery + machine_use
if current_time < machine_first_use:
wait_time = 0
elif current_time == machine_first_use:
wait_time = machine_use
else:
if current_time % one_cycle > (machine_first_use + machine_offset + machine_use) % one_cycle:
wait_time = 0
elif current_time % one_cycle == (machine_first_use + machine_offset + machine_use) % one_cycle:
wait_time = machine_use
else:
wait_time = (machine_first_use + machine_offset + machine_use) % one_cycle - current_time % one_cycle
new_offset = 0
time_after_jim_use = current_time + wait_time + jim_use
if time_after_jim_use < machine_first_use:
new_offset = 0
else:
new_offset = time_after_jim_use - ((time_after_jim_use + machine_offset) // one_cycle) * one_cycle
return time_after_jim_use + jim_recovery, new_offset
temp_jim = [*map(int, input().split())]
jim = [[temp_jim[2*i], temp_jim[2*i+1]] for i in range(10)]
machines = [[*map(int, input().split())] for _ in [0]*10]
offset = [0 for _ in range(10)]
current_time = 0
for _ in range(3):
for machine_using in range(10):
current_time, new_offset = workout(*jim[machine_using], *machines[machine_using], offset[machine_using], current_time)
offset[machine_using] = new_offset
print(current_time)
Issues:
- Fixed order (1–10, three times) isn’t optimal.
- Modulo-based
offset
doesn’t correctly handle the other person’s schedule shifts. - Outputs final time including recovery, not just machine 10’s usage end.
Latest Attempt (Also WA)
I tried a greedy approach, selecting the machine with the earliest start time, using 1D arrays (uses_left
for remaining uses, next_usage
for the other person’s next usage time). The other person’s schedule is updated to the next cycle boundary after Jim’s usage. It still fails the sample case (doesn’t output 100
) and gets WA on Kattis.
def get_next_start(jim_use, machine_use, machine_recovery, machine_first_use, current_time, next_usage):
cycle = machine_use + machine_recovery
start_time = current_time
k = max(0, (current_time - machine_first_use + cycle - 1) // cycle)
while True:
usage_start = max(machine_first_use + k * cycle, next_usage)
usage_end = usage_start + machine_use
if start_time < usage_start:
return start_time, usage_start
elif start_time == usage_start:
return usage_end, usage_start # Politeness: Jim waits
elif usage_start < start_time < usage_end:
return usage_end, usage_start
k += 1
# Read input
temp_jim = list(map(int, input().split()))
jim = [[temp_jim[2*i], temp_jim[2*i+1]] for i in range(10)]
machines = [list(map(int, input().split())) for _ in range(10)]
uses_left = [3] * 10 # 1D array: remaining uses
next_usage = [m[2] for m in machines] # 1D array: other person's next usage time
current_time = 0
last_machine10_end = 0
# Simulate 30 uses
for _ in range(30):
earliest_start = float('inf')
best_machine = -1
best_usage_start = None
for i in range(10):
if uses_left[i] > 0:
start_time, usage_start = get_next_start(jim[i][0], machines[i][0], machines[i][1], machines[i][2], current_time, next_usage[i])
if start_time < earliest_start:
earliest_start = start_time
best_machine = i
best_usage_start = usage_start
if best_machine == -1:
break
jim_end = earliest_start + jim[best_machine][0]
# Update other person's next usage
cycle = machines[best_machine][0] + machines[best_machine][1]
k = max(0, (jim_end - machines[best_machine][2] + cycle - 1) // cycle)
next_usage[best_machine] = machines[best_machine][2] + k * cycle
if next_usage[best_machine] < jim_end:
next_usage[best_machine] += cycle
current_time = jim_end + jim[best_machine][1] # Update to end of recovery
uses_left[best_machine] -= 1
if best_machine == 9:
last_machine10_end = jim_end # End of usage, not recovery
print(last_machine10_end)
Issues:
- Doesn’t produce
100
for the sample input, suggesting a flaw in schedule updates or conflict handling. - The
next_usage
update to the next cycle boundary might be incorrect. - Possible edge cases (e.g., negative
machine_first_use
, simultaneous availability) are mishandled.
Book’s Hint
The book suggests this is a "gym simulation" problem and recommends using 1D arrays to simulate time quickly. I’ve used arrays for uses_left
and next_usage
, but I’m not getting the sample output or passing Kattis tests.
Questions
- How should the two-way waiting rule ("Jim’s usage sometimes results in the other people having to wait as well") be implemented? Is the other person’s schedule supposed to shift to the next cycle boundary after Jim’s usage, or am I missing something?
- Is the greedy approach (earliest start time) correct, or do I need dynamic programming or another strategy?
- How do I correctly update the other person’s schedule after Jim’s usage? My latest attempt uses cycle boundaries, but it fails.
- Are there edge cases (e.g., negative
machine_first_use
, large times) I’m not handling? - Has anyone solved this on Kattis? Could you share pseudocode or point out where my approach fails?
- Why don’t either code produce
100
for the sample input? What’s wrong with the simulation?
I’m really stuck and would love any insights, pseudocode, or corrections. If you’ve solved this, how did you handle the scheduling and waiting rules? Thanks so much for your help!
POGtastic@reddit
This question has been bugging me, and now it's the weekend and I have some spare time to take a crack at it. It's actually easier than I originally thought it was (I first read it as "find the optimal workout, which requires dynamic programming).
In This House We Believe In Edwin Brady Thought
Edwin Brady defines his types first, and so do we. I like record syntax for this kind of thing, so we are all OCaml programmers on this blessed day. The record is going to contain the following:
In code:
Utility Functions
OCaml's stdlib is kinda bare-bones. They're getting better about it, but they're still missing a couple of things that I miss from some of the other MLs. They're also missing the
>>
operator from F#, which I'm partial to.Function Types
Jim is also going to have a
schedule list
, but it's immutable, so it's going to be its own separate argument to my function. Also, Jim'sfirst_use_time
isn't needed for any of his workouts, so we'll set all of those to 0 when I parse them.Our
go
function (common OCaml name for what's about to happen, aSeq.iterate
) is going to take two arguments: Jim's schedule (aschedule list
) and aframe
. It's going to return a newframe
, doing the following:remaining_workouts
,n
.schedule
and the other person'sschedule
by finding then
th element of these lists.t
, the other person'sschedule
, and Jim's schedule to determine two things: the other person's newschedule
, and the newt2
such that Jim has finished his workout and recovery time.Let's do #3 first and break it out into its own function, as it's the most complicated step.
The
do_workout
FunctionThere are two cases:
t
is less than the other person'sfirst_use_time
, ort
is inside the other person's recovery time, which means that Jim gets to work out with no waiting.t
is inside the other person's workout time, which means that Jim needs to wait until that person'suse_time
is done.In either case, we need to update the other person's schedule, since Jim's
use_time
might be large. So even if Jim patiently waits his turn and goes immediately after the other person is done working out, that might still delay the other person's next workout start if Jim is slow enough on that machine. We actually have a really easy way to update the other person's schedule: we just set the newfirst_start_time
to the maximum of Jim's workout finish and the existing start of the other guy's next workout.Let's run through a few test cases. Suppose that we have
Running at
t=5
, Jim has the machine to himself, so he's going to take 12 + 15 seconds to do his workout (and thus the newt
is 32). The other guy's workout start remains unchanged, since Jim is done with the machine att=17
.Running at
t=15
, Jim gets the machine and takes 12 + 15 seconds to do his workout (and thus the newt
is 42). But the other guy's workout start has to be updated, since Jim is still working out att=20
, and isn't done untilt=27
. So the other guy'sfirst_workout_time
is nowt=27
.And lastly, running at
t=25
, the other guy is now working out, and Jim has to wait untilt=30
to start working out (and thus the newt
is 57). The other guy's workout start also has to be updated, since Jim is still working out att=40
. So the other guy'sfirst_workout_time
is nowt=42
.Defining The
go
FunctionIt's probably easier just to write the code. I am popping off the head of the
remaining
workouts, getting the schedules associated with that workout's index, and performingdo_workout
to get the new time and the new schedule. I then update the schedule, time, and remaining workouts.Total Time
We're going to do the following:
go
over and over again until we run out of workouts.t
.recovery_time
.Thus
Parsing
OCaml makes this pretty easy.
Main Function
I generally assume that I'm just taking from stdin and printing to stdout, which means that we turn
stdin
into a list of lines, runparse_file
, and then runtotal_time
on the resulting lists ofschedule
s.All Together, With Feeling
Running on the provided test case:
$ ocamlc blarg.ml -o blarg $ ./blarg <<< "5 5 3 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 1 8 3 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0 1 1 0" 100
Doing this in Python is left as an exercise for the reader. Personally, I would define my own
Schedule
andFrame
classes. You can also use objects as extremely bare-bones record types.Excellent_Dingo_7109@reddit (OP)
For people attempting the problem but are stuck, like me:
The Kattis "Workout for a Dumbbell" problem is about Jim planning his gym workout on 10 machines, using each machine three times. Another person is also using these machines, and they take turns based on specific rules. The goal is to figure out when Jim finishes his last use of the 10th machine, not counting his final rest period.
Excellent_Dingo_7109@reddit (OP)
Collecting Information:
Excellent_Dingo_7109@reddit (OP)
Simulating the Workout:
Excellent_Dingo_7109@reddit (OP)
Deciding When Jim Can Start:
Excellent_Dingo_7109@reddit (OP)
Final Answer:
Excellent_Dingo_7109@reddit (OP)
Edge Cases To Be Aware Of:
Excellent_Dingo_7109@reddit (OP)
Core Approach:
POGtastic@reddit
This looks like a dynamic programming problem to me. Consider the idea of a frame containing the following things:
t
And consider a function
do_workout(frame, idx)
that returns a new frame. We're going tot
by however long it takes Jim to wait for the machine to open up, do his workout, and recoverFor the problem itself, set up a min-heap, ordered by
t
. We are going tot
- that's the quickest amount of time for Jim to do his workouts. You might need to check with the problem prompt to see if the very last workout's recovery time counts toward the total workout time, or if you need to discard it^([1]).do_workout
on every non-zero index and add the resulting frames to the heap.The natural result of this is that inefficient workouts (meaning that you've waited a long time) get put into the depths of the heap, while the most efficient scheduling (which keeps
t
low) gets popped off the heap more quickly.The starting heap contains a single frame where
t=0
, the counts are a list of 3s, and the schedules are what is provided to you.^[1] You can always track the recovery time as another field in the frame, and increment
t
by that recovery time after you've verified that Jim still has a workout to do. The initial frame will, of course, set that recovery time to 0.