Tasks And Deadlines
Problem Overview
| Attribute | Value |
|---|---|
| Difficulty | Medium |
| Category | Greedy / Sorting |
| Time Limit | 1 second |
| Key Technique | Greedy (Sort by Duration) |
| CSES Link | Tasks and Deadlines |
Learning Goals
After solving this problem, you will be able to:
- Recognize scheduling problems that can be solved greedily
- Understand why sorting by duration (Shortest Job First) maximizes reward
- Derive greedy sorting criteria using algebraic manipulation
- Handle potential integer overflow in scheduling problems
Problem Statement
Problem: You have n tasks. Each task has a duration and a deadline. You must complete all tasks, and for each task, you earn a reward equal to deadline - finish_time. Find the maximum total reward.
Input:
- Line 1: Integer n (number of tasks)
- Lines 2 to n+1: Two integers d and t (duration and deadline of each task)
Output:
- Maximum total reward (can be negative)
Constraints:
- 1 <= n <= 2 x 10^5
- 1 <= d[i], t[i] <= 10^6
Example
Input:
3
6 10
8 15
5 12
Output:
2
Explanation:
- Optimal order: Task 3 (d=5), Task 1 (d=6), Task 2 (d=8)
- Task 3: finish at time 5, reward = 12 - 5 = 7
- Task 1: finish at time 11, reward = 10 - 11 = -1
- Task 2: finish at time 19, reward = 15 - 19 = -4
- Total: 7 + (-1) + (-4) = 2
Intuition: How to Think About This Problem
Pattern Recognition
Key Question: The reward formula is
deadline - finish_time. Since we must complete ALL tasks, total time spent is fixed (sum of all durations). What can we control?
The total reward is: sum of (deadline_i - finish_time_i)
This equals: (sum of deadlines) - (sum of finish times)
Since sum of deadlines is fixed, we need to minimize the sum of finish times.
Breaking Down the Problem
- What are we looking for? Maximum total reward = Minimize sum of finish times
- What information do we have? Duration and deadline for each task
- What’s the relationship? Earlier tasks contribute their duration to ALL subsequent finish times
The Key Insight: Shortest Job First
Consider two adjacent tasks A and B. If we swap their order:
- Order A then B: Task A finishes at time T + d_A, Task B at T + d_A + d_B
- Order B then A: Task B finishes at time T + d_B, Task A at T + d_B + d_A
The sum of finish times for these two tasks:
- Order A-B: (T + d_A) + (T + d_A + d_B) = 2T + 2*d_A + d_B
- Order B-A: (T + d_B) + (T + d_B + d_A) = 2T + 2*d_B + d_A
A-B is better when: 2d_A + d_B < 2d_B + d_A, which simplifies to d_A < d_B
Conclusion: Always do shorter tasks first!
Analogy
Think of it like standing in a queue at the bank. If you want to minimize the total waiting time for everyone, you should serve customers with the shortest transactions first. This is the classic “Shortest Job First” scheduling algorithm.
Solution 1: Brute Force
Idea
Try all possible orderings (permutations) of tasks and find the one with maximum reward.
Algorithm
- Generate all n! permutations of tasks
- For each permutation, calculate total reward
- Return the maximum reward found
Code
from itertools import permutations
def solve_brute_force(tasks):
"""
Brute force: try all permutations.
Time: O(n! * n)
Space: O(n)
"""
max_reward = float('-inf')
for order in permutations(tasks):
current_time = 0
total_reward = 0
for duration, deadline in order:
current_time += duration
total_reward += deadline - current_time
max_reward = max(max_reward, total_reward)
return max_reward
Complexity
| Metric | Value | Explanation |
|---|---|---|
| Time | O(n! * n) | n! permutations, O(n) to evaluate each |
| Space | O(n) | Store one permutation at a time |
Why This Works (But Is Slow)
Correctness is guaranteed because we check every possible ordering. However, n! grows extremely fast - even for n=12, we have 479 million permutations.
Solution 2: Optimal - Greedy (Sort by Duration)
Key Insight
The Trick: Sort tasks by duration in ascending order. Shorter tasks first always yields the maximum reward.
Why Sorting by Duration Works
From our analysis above, for any two adjacent tasks, placing the shorter one first is always better or equal. By sorting all tasks by duration, we ensure every adjacent pair is optimally ordered, which means the entire sequence is optimal.
Algorithm
- Sort tasks by duration (ascending)
- Process tasks in sorted order, accumulating time and reward
- Return total reward
Dry Run Example
Let’s trace through with input [(6,10), (8,15), (5,12)]:
Initial: tasks = [(6,10), (8,15), (5,12)]
Step 1: Sort by duration
Sorted: [(5,12), (6,10), (8,15)]
Step 2: Process in order
current_time = 0, total_reward = 0
Process (5, 12):
current_time = 0 + 5 = 5
reward = 12 - 5 = 7
total_reward = 0 + 7 = 7
Process (6, 10):
current_time = 5 + 6 = 11
reward = 10 - 11 = -1
total_reward = 7 + (-1) = 6
Process (8, 15):
current_time = 11 + 8 = 19
reward = 15 - 19 = -4
total_reward = 6 + (-4) = 2
Result: 2
Visual Diagram
Tasks sorted by duration:
Task: (5,12) (6,10) (8,15)
|--5--| |---6---| |----8----|
Time: 0 5 11 19
Rewards:
Task 1: deadline=12, finish=5, reward = +7
Task 2: deadline=10, finish=11, reward = -1
Task 3: deadline=15, finish=19, reward = -4
Total = +2
Code
Python Solution:
def solve(n, tasks):
"""
Optimal greedy solution: sort by duration.
Time: O(n log n)
Space: O(1) extra (in-place sort)
"""
# Sort by duration (shortest first)
tasks.sort(key=lambda x: x[0])
current_time = 0
total_reward = 0
for duration, deadline in tasks:
current_time += duration
total_reward += deadline - current_time
return total_reward
def main():
n = int(input())
tasks = []
for _ in range(n):
d, t = map(int, input().split())
tasks.append((d, t))
print(solve(n, tasks))
if __name__ == "__main__":
main()
Complexity
| Metric | Value | Explanation |
|---|---|---|
| Time | O(n log n) | Dominated by sorting |
| Space | O(1) | In-place sort, only tracking two variables |
Common Mistakes
Mistake 1: Sorting by Deadline Instead of Duration
# WRONG
tasks.sort(key=lambda x: x[1]) # Sort by deadline
# CORRECT
tasks.sort(key=lambda x: x[0]) # Sort by duration
Problem: Sorting by deadline seems intuitive (do urgent tasks first) but does not minimize the sum of finish times.
Fix: Remember the mathematical proof - only duration affects the sum of finish times.
Problem: With n = 2x10^5 tasks and durations up to 10^6, total time can reach 2x10^11, exceeding int range. The reward can also be very negative.
Fix: Use long long for time and reward calculations.
Mistake 3: Forgetting Rewards Can Be Negative
# WRONG - early termination on negative reward
if total_reward < 0:
break
# CORRECT - must complete ALL tasks
for duration, deadline in tasks:
current_time += duration
total_reward += deadline - current_time # May be negative
Problem: The problem requires completing ALL tasks. Negative rewards are expected and valid.
Fix: Process all tasks regardless of individual reward signs.
Edge Cases
| Case | Input | Expected Output | Why |
|---|---|---|---|
| Single task | n=1, (5,10) |
5 | 10 - 5 = 5 |
| All same duration | (3,5), (3,8), (3,2) |
Same any order | Order doesn’t matter |
| All negative rewards | (10,1), (10,2) |
-27 | 1-10 + 2-20 = -9 + -18 |
| Large values | n=2x10^5, d=10^6 |
Use long long | Sum can exceed 32-bit |
| Deadline < Duration | (10, 5) |
-5 | Valid input, negative reward |
When to Use This Pattern
Use This Approach When:
- You need to schedule ALL tasks (no selection involved)
- Reward/penalty depends on completion time
- Tasks are independent (no dependencies)
- You want to minimize weighted completion time
Don’t Use When:
- Tasks have dependencies (use topological sort)
- You can skip tasks (use different greedy or DP)
- Rewards have complex formulas not linear in finish time
- Tasks have different weights/priorities beyond duration
Pattern Recognition Checklist:
- Must complete all items? -> Consider ordering optimization
- Penalty/reward based on completion time? -> Think “Shortest Job First”
- Can prove adjacent swap property? -> Greedy by sorting works
Related Problems
Easier (Do These First)
| Problem | Why It Helps |
|---|---|
| Movie Festival (CSES 1629) | Basic interval scheduling, sort by end time |
| Stick Lengths (CSES 1074) | Greedy with sorting, find optimal target |
Similar Difficulty
| Problem | Key Difference |
|---|---|
| Reading Books (CSES 1631) | Two readers, similar scheduling |
| Movie Festival II (CSES 1632) | Multiple people attending movies |
Harder (Do These After)
| Problem | New Concept |
|---|---|
| Task Scheduler (LC 621) | Cooldown constraints |
| Course Schedule III (LC 630) | Task selection + scheduling |
| Weighted Job Scheduling (LC 1235) | DP with binary search |
Key Takeaways
- The Core Idea: Sort by duration (Shortest Job First) to minimize sum of finish times
- Time Optimization: From O(n!) brute force to O(n log n) with greedy sorting
- Space Trade-off: No extra space needed beyond input storage
- Pattern: Adjacent swap argument proves greedy correctness
Practice Checklist
Before moving on, make sure you can:
- Prove why sorting by duration is optimal using the swap argument
- Implement the solution without looking at code
- Explain why sorting by deadline does NOT work
- Handle integer overflow correctly in C++
- Recognize similar “minimize completion time” problems
Mathematical Proof (Optional Deep Dive)
Theorem: Shortest Job First is Optimal
Claim: Sorting tasks by duration minimizes the sum of finish times.
Proof by Exchange Argument:
- Consider any optimal ordering that is NOT sorted by duration
- There must exist adjacent tasks i and j where d_i > d_j but i comes before j
- Let T be the time before task i starts
- Sum of finish times for i,j: (T + d_i) + (T + d_i + d_j) = 2T + 2d_i + d_j
- If we swap: (T + d_j) + (T + d_j + d_i) = 2T + 2d_j + d_i
- Since d_i > d_j: 2d_i + d_j > 2d_j + d_i
- Therefore swapping improves (or equals) the sum
- Repeat until sorted - we can only improve, never worsen
- Thus sorted order is optimal
This is the classic “bubble sort correctness” style proof for greedy algorithms.