Greedy
Problems solved using greedy algorithms that make locally optimal choices at each step with the goal of finding a global optimum.
Problems
Building Permutation
Problem Information
- Source: Codeforces
- Difficulty: Secret
- Time Limit: 1000ms
- Memory Limit: 256MB
Problem Statement
Permutation p is an ordered set of integers p1, p2, …, pn, consisting of n distinct positive integers, each of them doesn’t exceed n. We’ll denote the i-th element of permutation p as pi. We’ll call number n the size or the length of permutation p1, p2, …, pn.
You have a sequence of integers a1, a2, …, an. In one move, you are allowed to decrease or increase any number by one. Count the minimum number of moves needed to build a permutation from this sequence.
Input Format
- The first line contains integer n (1 ≤ n ≤ 3 × 10^5) - the size of the sought permutation.
- The second line contains n integers a1, a2, …, an (-10^9 ≤ ai ≤ 10^9).
Output Format
Print a single number - the minimum number of moves.
Sample
Input:
2
3 0
Output:
2
Input:
3
-1 -1 2
Output:
6
Solution
Approach
The key insight is that to minimize the total number of moves, we should sort the array and then match each element to the corresponding position in the permutation [1, 2, 3, …, n]. After sorting, the i-th smallest element should become i.
Python Solution
def solve():
n = int(input())
a = list(map(int, input().split()))
# Sort the array
a.sort()
# Calculate minimum moves by matching sorted array to [1, 2, ..., n]
total_moves = 0
for i in range(n):
total_moves += abs(a[i] - (i + 1))
print(total_moves)
if __name__ == "__main__":
solve()
Complexity Analysis
- Time Complexity: O(n log n) due to sorting
- Space Complexity: O(n) for storing the array
Making Jumps
Problem Information
- Source: SPOJ
- Difficulty: Secret
- Time Limit: 1000ms
- Memory Limit: 512MB
Problem Statement
A knight is a piece used in the game of chess. The chessboard itself is a square array of cells. Each time a knight moves, its resulting position is two rows and one column, or two columns and one row away from its starting position. Thus a knight starting on row r, column c - which we’ll denote as (r, c) - can move to any of the squares (r-2, c-1), (r-2, c+1), (r-1, c-2), (r-1, c+2), (r+1, c-2), (r+1, c+2), (r+2, c-1), or (r+2, c+1). Of course, the knight may not move to any square that is not on the board.
Suppose the chessboard is not square, but instead has rows with variable numbers of columns, and with each row offset zero or more columns to the right of the row above it. How many of the squares in such a modified chessboard can a knight, starting in the upper left square (marked with an asterisk), not reach in any number of moves without resting in any square more than once?
If necessary, the knight is permitted to pass over regions that are outside the borders of the modified chessboard, but as usual, it can only move to squares that are within the borders of the board.
Input Format
- Each test case starts with the number of rows n (1 ≤ n ≤ 10).
- For each row, there are two integers: the offset (number of cells to skip) and the number of cells in that row.
- Input is terminated when n = 0.
Output Format
For each test case, print “Case X, Y squares can not be reached.” where X is the case number and Y is the number of unreachable squares.
Solution
Approach
This is a backtracking/DFS problem. We need to:
- Build the board representation with offsets
- Use DFS/backtracking to explore all possible paths from the starting position
- Track visited squares and count maximum reachable squares
- The answer is total squares minus maximum reachable squares
Python Solution
def solve():
# Knight move offsets
moves = [(-2, -1), (-2, 1), (-1, -2), (-1, 2),
(1, -2), (1, 2), (2, -1), (2, 1)]
case_num = 0
while True:
n = int(input())
if n == 0:
break
case_num += 1
# Read board configuration
rows = []
total_squares = 0
for i in range(n):
offset, count = map(int, input().split())
rows.append((offset, count))
total_squares += count
# Create board: board[r] contains set of valid columns for row r
board = []
for offset, count in rows:
board.append(set(range(offset, offset + count)))
# Find starting position (first cell in first row)
start_r, start_c = 0, rows[0][0]
# DFS with backtracking to find maximum reachable
max_reached = [0]
def is_valid(r, c):
return 0 <= r < n and c in board[r]
def dfs(r, c, visited):
max_reached[0] = max(max_reached[0], len(visited))
for dr, dc in moves:
nr, nc = r + dr, c + dc
if is_valid(nr, nc) and (nr, nc) not in visited:
visited.add((nr, nc))
dfs(nr, nc, visited)
visited.remove((nr, nc))
visited = {(start_r, start_c)}
dfs(start_r, start_c, visited)
unreachable = total_squares - max_reached[0]
print(f"Case {case_num}, {unreachable} squares can not be reached.")
if __name__ == "__main__":
solve()
Optimized
def solve():
moves = [(-2, -1), (-2, 1), (-1, -2), (-1, 2),
(1, -2), (1, 2), (2, -1), (2, 1)]
case_num = 0
while True:
n = int(input())
if n == 0:
break
case_num += 1
# Read board
rows = []
total_squares = 0
max_col = 0
for i in range(n):
offset, count = map(int, input().split())
rows.append((offset, count))
total_squares += count
max_col = max(max_col, offset + count)
# Create 2D grid for faster lookup
# grid[r][c] = True if cell exists
grid = [[False] * max_col for _ in range(n)]
for r, (offset, count) in enumerate(rows):
for c in range(offset, offset + count):
grid[r][c] = True
start_r, start_c = 0, rows[0][0]
max_reached = [0]
def is_valid(r, c):
return 0 <= r < n and 0 <= c < max_col and grid[r][c]
def dfs(r, c, count, visited):
max_reached[0] = max(max_reached[0], count)
# Early termination if we've reached all squares
if count == total_squares:
return
for dr, dc in moves:
nr, nc = r + dr, c + dc
if is_valid(nr, nc) and not visited[nr][nc]:
visited[nr][nc] = True
dfs(nr, nc, count + 1, visited)
visited[nr][nc] = False
visited = [[False] * max_col for _ in range(n)]
visited[start_r][start_c] = True
dfs(start_r, start_c, 1, visited)
unreachable = total_squares - max_reached[0]
print(f"Case {case_num}, {unreachable} squares can not be reached.")
if __name__ == "__main__":
solve()
Complexity Analysis
- Time Complexity: O(8^S) in worst case where S is total squares, but pruning significantly reduces this
- Space Complexity: O(S) for visited array and recursion stack
Key Insight
This is a Hamiltonian path variant - we want to find the longest path visiting each square at most once. Backtracking explores all possible paths, and we keep track of the maximum number of squares reached. The answer is the total squares minus this maximum.
Petya and Catacombs
Problem Information
- Source: Codeforces
- Difficulty: Secret
- Time Limit: 1000ms
- Memory Limit: 256MB
Problem Statement
Petya explores Paris catacombs. Every minute he moves through a passage to another room. When entering a room at minute i, he notes:
- If visited before: the minute he was last in this room
- Otherwise: any non-negative integer strictly less than i
Initially at minute 0, Petya was in a room (no note for t₀). Find the minimum possible number of rooms.
Input Format
- First line: n (1 ≤ n ≤ 2×10⁵) - number of notes
- Second line: n integers t₁, t₂, …, tₙ (0 ≤ tᵢ < i)
Output Format
Print the minimum possible number of rooms in Paris catacombs.
Solution
Approach
Track which “last visit times” are available. When Petya enters a room:
- If tᵢ was a previous minute where a room was last visited, he’s revisiting that room
- Otherwise, he’s entering a new room
Use a set to track available “last visit” times. Initially, time 0 is available (starting room).
Python Solution
def solve():
n = int(input())
times = list(map(int, input().split()))
available = {0} # Times when rooms were last visited
rooms = 1 # Start with 1 room (the initial room)
for i, t in enumerate(times, 1):
if t in available:
# Revisiting a room - remove old time, add current time
available.remove(t)
available.add(i)
else:
# New room
rooms += 1
available.add(i)
print(rooms)
if __name__ == "__main__":
solve()
Alternative
def solve():
n = int(input())
notes = list(map(int, input().split()))
# Track which timestamps are "used" (a room was last visited at that time)
used = [False] * (n + 1)
used[0] = True # Initial room at time 0
rooms = 1
for i in range(n):
t = notes[i]
current_time = i + 1
if used[t]:
# Revisiting room that was last visited at time t
used[t] = False
used[current_time] = True
else:
# Must be a new room
rooms += 1
used[current_time] = True
print(rooms)
if __name__ == "__main__":
solve()
Complexity Analysis
- Time Complexity: O(N)
- Space Complexity: O(N)
Key Insight
Each room has a “last visited time”. When we see note tᵢ, if time t was previously a room’s last-visit-time, we can revisit that room (updating its last-visit to current time i). Otherwise, we must create a new room. Greedy assignment minimizes rooms.
Roma and Changing Signs
Problem Information
- Source: Codeforces
- Difficulty: Secret
- Time Limit: 2000ms
- Memory Limit: 256MB
Problem Statement
Roma works in a company that sells TVs. Now he has to prepare a report for the last year.
Roma has got a list of the company’s incomes. The list is a sequence that consists of n integers. The total income of the company is the sum of all integers in sequence. Roma decided to perform exactly k changes of signs of several numbers in the sequence. He can also change the sign of a number one, two or more times.
The operation of changing a number’s sign is the operation of multiplying this number by -1.
Help Roma perform the changes so as to make the total income of the company (the sum of numbers in the resulting sequence) maximum. Note that Roma should perform exactly k changes.
Input Format
- The first line contains two integers n and k (1 ≤ n, k ≤ 10^5), showing how many numbers are in the sequence and how many swaps are to be made.
-
The second line contains a non-decreasing sequence, consisting of n integers ai ( ai ≤ 10^4). The numbers in the lines are separated by single spaces. Please note that the given sequence is sorted in non-decreasing order.
Output Format
In the single line print the answer to the problem - the maximum total income that we can obtain after exactly k changes.
Solution
Approach
- First, flip all negative numbers to positive (starting from the most negative) as long as we have operations left.
- After flipping all negatives, if we still have operations left:
- If remaining operations is even, we can flip any number twice (no net change)
- If remaining operations is odd, flip the smallest absolute value number once
Python Solution
def solve():
n, k = map(int, input().split())
a = list(map(int, input().split()))
# Flip negative numbers starting from smallest (most negative)
for i in range(n):
if a[i] < 0 and k > 0:
a[i] = -a[i]
k -= 1
# Sort again to find the minimum element
a.sort()
# If k is odd, flip the smallest element
if k % 2 == 1:
a[0] = -a[0]
print(sum(a))
if __name__ == "__main__":
solve()
Complexity Analysis
- Time Complexity: O(n log n) due to sorting
- Space Complexity: O(n) for storing the array
The Number On The Board
Problem Information
- Source: Codeforces
- Difficulty: Secret
- Time Limit: 2000ms
- Memory Limit: 256MB
Problem Statement
Some natural number was written on the board. Its sum of digits was not less than k. But you were distracted a bit, and someone changed this number to n, replacing some digits with others. It’s known that the length of the number didn’t change.
You have to find the minimum number of digits in which these two numbers can differ.
Input Format
- The first line contains integer k (1 ≤ k ≤ 10^9).
- The second line contains integer n (1 ≤ n < 10^100000).
- There are no leading zeros in n. It’s guaranteed that this situation is possible.
Output Format
Print the minimum number of digits in which the initial number and n can differ.
Sample
Input:
3
11
Output:
1
Input:
3
99
Output:
0
Solution
Approach
To minimize the number of changed digits while making the digit sum at least k:
- Calculate current digit sum
- If sum ≥ k, answer is 0
- Otherwise, greedily change the smallest digits to 9 (maximizing gain per change)
- Sort digits and change from smallest to largest until sum ≥ k
Python Solution
def solve():
k = int(input())
n = input().strip()
# Get all digits
digits = [int(c) for c in n]
current_sum = sum(digits)
# If already >= k, no changes needed
if current_sum >= k:
print(0)
return
# Sort digits to change smallest ones first (maximize gain)
digits.sort()
changes = 0
i = 0
while current_sum < k:
# Change digit to 9
gain = 9 - digits[i]
if gain > 0:
changes += 1
current_sum += gain
i += 1
print(changes)
if __name__ == "__main__":
solve()
Complexity Analysis
- Time Complexity: O(n log n) where n is the number of digits, due to sorting
- Space Complexity: O(n) for storing digits
Through the Desert
Problem Information
- Source: UVA
- Difficulty: Secret
- Time Limit: 1000ms
- Memory Limit: 512MB
Problem Statement
Cross a desert with a jeep. The fuel tank must be large enough to complete the journey. Events along the way:
- Fuel consumption n: Truck uses n litres per 100 km
- Leak: Adds 1 litre/km leak rate (cumulative)
- Gas station: Fill up tank completely
- Mechanic: Fixes all leaks (leak rate becomes 0)
- Goal: Journey’s end
Find the minimum tank size (in litres) to complete the journey.
Input Format
- Multiple test cases (up to 50 events each)
- Events given by: distance (km from start) and event type
- First event: “0 Fuel consumption n”
- Last event: “d Goal”
- Events sorted by distance
- Input ends with “0 Fuel consumption 0”
Output Format
For each test case, print minimum tank volume with 3 decimal places.
Solution
Approach
Simulate the journey backwards or use binary search on tank size.
Key insight: The minimum tank size is determined by the segment that requires the most fuel between refills (gas stations or start).
For each segment between gas stations:
- Track fuel consumption rate and leak rate
- Calculate fuel needed for each sub-segment
- The tank must hold enough for the worst segment
Python Solution
def solve():
while True:
events = []
while True:
line = input().split()
dist = int(line[0])
event_type = line[1]
if event_type == "Fuel":
consumption = int(line[3])
events.append((dist, "fuel", consumption))
if dist == 0 and consumption == 0:
return
elif event_type == "Leak":
events.append((dist, "leak", 0))
elif event_type == "Gas":
events.append((dist, "gas", 0))
elif event_type == "Mechanic":
events.append((dist, "mechanic", 0))
elif event_type == "Goal":
events.append((dist, "goal", 0))
break
# Simulate to find minimum tank size
# Binary search on tank capacity
def can_complete(tank_capacity):
fuel = tank_capacity
consumption = 0 # litres per 100 km
leak_rate = 0 # litres per km
prev_dist = 0
for dist, event, val in events:
delta = dist - prev_dist
if delta > 0:
# Fuel used = (consumption/100) * delta + leak_rate * delta
fuel_used = (consumption / 100) * delta + leak_rate * delta
fuel -= fuel_used
if fuel < -1e-9:
return False
if event == "fuel":
consumption = val
elif event == "leak":
leak_rate += 1
elif event == "gas":
fuel = tank_capacity
elif event == "mechanic":
leak_rate = 0
elif event == "goal":
pass
prev_dist = dist
return fuel >= -1e-9
# Binary search for minimum tank capacity
lo, hi = 0.0, 1e9
for _ in range(100): # Enough iterations for precision
mid = (lo + hi) / 2
if can_complete(mid):
hi = mid
else:
lo = mid
print(f"{hi:.3f}")
if __name__ == "__main__":
solve()
Alternative
def solve():
while True:
events = []
while True:
parts = input().split()
dist = int(parts[0])
event = parts[1]
if event == "Fuel":
rate = int(parts[3])
if dist == 0 and rate == 0:
return
events.append((dist, "fuel", rate))
elif event == "Leak":
events.append((dist, "leak", 0))
elif event == "Gas":
events.append((dist, "gas", 0))
elif event == "Mechanic":
events.append((dist, "mech", 0))
elif event == "Goal":
events.append((dist, "goal", 0))
break
# Find min tank by checking each segment between gas stations
# Tank must be large enough for worst segment
def simulate(capacity):
tank = capacity
cons = 0
leak = 0
pos = 0
for d, e, v in events:
dist = d - pos
if dist > 0:
usage = cons * dist / 100 + leak * dist
tank -= usage
if tank < -1e-9:
return False
if e == "fuel":
cons = v
elif e == "leak":
leak += 1
elif e == "gas":
tank = capacity
elif e == "mech":
leak = 0
pos = d
return True
lo, hi = 0, 2e7
for _ in range(100):
mid = (lo + hi) / 2
if simulate(mid):
hi = mid
else:
lo = mid
print(f"{hi:.3f}")
if __name__ == "__main__":
solve()
Complexity Analysis
- Time Complexity: O(E × log(MAX_CAPACITY)) where E is number of events
- Space Complexity: O(E)
Key Insight
Binary search on tank capacity. For each candidate capacity, simulate the journey: track fuel level, consumption rate, and leak rate. The tank is filled at gas stations and at the start. Leaks accumulate (each “Leak” event adds 1 L/km). Mechanic resets leak rate to 0. The minimum capacity is the smallest value that allows completing the journey without running out.
Wine trading in Gergovia
Problem Information
- Source: SPOJ
- Difficulty: Secret
- Time Limit: 1000ms
- Memory Limit: 512MB
Problem Statement
Gergovia consists of one street, and every inhabitant of the city is a wine salesman. Everyone buys wine from other inhabitants of the city. Every day each inhabitant decides how much wine he wants to buy or sell. Interestingly, demand and supply is always the same, so that each inhabitant gets what he wants.
There is one problem, however: Transporting wine from one house to another results in work. Since all wines are equally good, the inhabitants of Gergovia don’t care which persons they are doing trade with, they are only interested in selling or buying a specific amount of wine.
In this problem you are asked to reconstruct the trading during one day in Gergovia. For simplicity we will assume that the houses are built along a straight line with equal distance between adjacent houses. Transporting one bottle of wine from one house to an adjacent house results in one unit of work.
Input Format
- The input consists of several test cases.
- Each test case starts with the number of inhabitants N (2 ≤ N ≤ 100000).
- The following line contains n integers ai (-1000 ≤ ai ≤ 1000).
- If ai ≥ 0, it means that the inhabitant living in the i-th house wants to buy ai bottles of wine.
- If ai < 0, he wants to sell -ai bottles of wine.
- You may assume that the numbers ai sum up to 0.
- The last test case is followed by a line containing 0.
Output Format
For each test case print the minimum amount of work units needed so that every inhabitant has his demand fulfilled.
Solution
Approach
The key insight is to think about the flow of wine between adjacent houses. At each position, we track the cumulative “debt” - how much wine needs to flow past that point. The total work is the sum of absolute values of this running balance.
Python Solution
def solve():
while True:
n = int(input())
if n == 0:
break
a = list(map(int, input().split()))
# Calculate total work using prefix sum approach
total_work = 0
carry = 0 # Wine that needs to be transported to the right
for i in range(n):
carry += a[i]
total_work += abs(carry)
print(total_work)
if __name__ == "__main__":
solve()
Complexity Analysis
- Time Complexity: O(n) per test case
- Space Complexity: O(n) for storing the array
Explanation
Think of it as: between each pair of adjacent houses, some wine needs to flow. If house i has demand a[i], the wine flowing between house i and i+1 is the cumulative sum up to i. The work done is |cumulative_sum| for each edge.