Quick Reference
Use this as a reminder after you have met an idea in its unit. The indented lines belong to the line above them. Every contest program reads its whole input, works out the answer, and prints exactly that answer.
Reading the input · Unit 1
import sys
data = sys.stdin.read() # all of standard input
tokens = data.split() # split on spaces and newlines
n = int(tokens[0]) # a token is text: convert it
values = []
for position in range(n): # N first, then N values
values.append(int(tokens[position + 1]))
A grid of R rows and C columns, read with a moving position:
rows = int(tokens[0])
columns = int(tokens[1])
position = 2
grid = []
for r in range(rows):
row = []
for c in range(columns):
row.append(int(tokens[position]))
position = position + 1
grid.append(row)
corner = grid[rows - 1][columns - 1] # grid[r][c]
Print values with one space between them, and nothing else:
output = ""
for position in range(len(values)):
if position > 0:
output = output + " "
output = output + str(values[position])
print(output)
Run a program with an input file: python my_solution.py < case.txt.
Boolean logic · Unit 2
allowed = (member or guest) and not banned
# not runs first, then and, then or
# De Morgan: not (a and b) is (not a) or (not b)
# not (a or b) is (not a) and (not b)
majority = yes_count * 2 > n # strict majority, no fractions
answer = tokens[1].lower() # compare without caring about case
False and … and True or … never run their right side.
Trace a program on paper before you run it: predict each line, then check.
Fast enough? · Unit 3
| Plan | Growth | Steps when N is 100,000 |
|---|---|---|
| Halve the range each step | O(log n) |
about 17 |
| One loop | O(n) |
100,000 |
| Sort, then one loop | O(n log n) |
about 1,700,000 |
| A loop inside a loop | O(n²) |
10,000,000,000 |
Read the constraints first, and compare the estimate with a budget of about 100,000,000 steps. A dictionary of values already seen turns a pair search into one loop:
seen = {}
for number in numbers:
if target - number in seen:
found = True
seen[number] = True
Sets · Unit 4
roster = {"Ava", "Bo", "Cy"} # a set literal
distinct = set() # an empty set
distinct.add("Dia") # adding a repeat changes nothing
distinct.discard("Bo") # remove it if it is there
if "Ava" in roster: # fast membership test
print("on the roster")
only_first = first - second # in first but not in second
print(len(distinct)) # how many distinct values
Build a union or an intersection with a loop:
both = set()
for name in first:
if name in second:
both.add(name)
Tuples and sorting with a key · Unit 4
records = [("Ava", 18), ("Bo", 9), ("Cy", 24)]
for name, score in records: # unpack each tuple
print(name, score)
def by_score(record):
return record[1]
def leaderboard_key(record):
name, score = record
return (-score, name) # highest score first, then name
lowest_first = sorted(records, key=by_score)
ranked = sorted(records, key=leaderboard_key)
Return a negated field, such as -score, to put the largest first.
Searching · Unit 5
ordered = sorted(values) # binary search needs a sorted list
found = False
lo = 0
hi = len(ordered) - 1
while lo <= hi and found == False:
mid = (lo + hi) // 2
if ordered[mid] == target:
found = True
elif ordered[mid] < target:
lo = mid + 1
else:
hi = mid - 1
Complete search tries every pair once:
count = 0
for first in range(n):
for second in range(first + 1, n):
if values[first] + values[second] == target:
count = count + 1
Search over the answer: find the smallest capacity that works, when a bigger capacity never needs more days.
lo = max(weights)
hi = sum(weights)
while lo < hi:
mid = (lo + hi) // 2
if feasible(mid): # your own check for one capacity
hi = mid
else:
lo = mid + 1
print(lo)
Greedy · Unit 6
def end_time(event):
return event[1]
attended = 0
last_end = -1
for event in sorted(events, key=end_time):
if event[0] >= last_end: # touching events do not overlap
attended = attended + 1
last_end = event[1]
coins.sort(reverse=True) # largest coin first
costs.sort() # cheapest first
Before you trust a greedy rule, try small counterexamples and tell the exchange story.
Simulation · Unit 7
charge = charge + change
if charge > capacity: # clamp to [0, capacity]
charge = capacity
elif charge < 0:
charge = 0
inside = 0 <= next_row and next_row < rows
inside = inside and 0 <= next_column and next_column < columns
if inside and grid[next_row][next_column] != -1:
robot_row = next_row
robot_column = next_column
seen = []
while state not in seen: # stop at the first repeat
seen.append(state)
state = (state * 2) % 7
Name the state, apply the rules in the given order, guard every edge, and make sure the loop stops.
Prefix sums · Unit 8
pre = [0]
for i in range(len(a)):
pre.append(pre[i] + a[i])
total = pre[right + 1] - pre[left] # a[left] through a[right]
A prefix grid has one extra zero row and one extra zero column:
above = pre[r][c + 1]
left = pre[r + 1][c]
overlap = pre[r][c]
pre[r + 1][c + 1] = grid[r][c] + above + left - overlap
total = pre[r2 + 1][c2 + 1] - pre[r1][c2 + 1]
total = total - pre[r2 + 1][c1] + pre[r1][c1]
Recursion and backtracking · Unit 9
def factorial(value):
if value == 0: # the base case stops the calls
return 1
return value * factorial(value - 1)
def search(index, total): # count subsets that hit target
if index == n:
if total == target:
return 1
return 0
with_value = search(index + 1, total + values[index])
without_value = search(index + 1, total)
return with_value + without_value
Mark a choice, recurse, then undo it before the next choice:
used[choice_index] = True
ways = ways + search(path + [choice])
used[choice_index] = False
Stacks and queues · Unit 10
from collections import deque
stack = deque()
stack.appendleft(item) # push
top = stack[0] # peek at the newest
stack.popleft() # pop the newest
queue = deque()
queue.append(item) # join at the back
first = queue.popleft() # serve the oldest
empty = len(queue) == 0 # check before you peek or pop
In postfix, pop the right value first, then the left: right = stack.popleft(), then left = stack.popleft().
Number systems and bits · Unit 11
digits = "0123456789ABCDEF"
text = ""
while number > 0: # decimal to another base
text = digits[number % base] + text
number = number // base # (the number 0 is "0")
value = 0
for bit in binary_text: # binary back to decimal
value = value * 2 + int(bit)
mask = 1 << k # only bit k is on
full_mask = (1 << width) - 1 # the lowest width bits on
if state & mask: # is bit k on?
print("on")
state = state | mask # set bit k
state = state & (full_mask ^ mask) # clear bit k
state = state ^ mask # flip bit k
flipped = ~state & full_mask # flip every bit in the width
for subset in range(1 << n): # every subset of n items
total = 0
for i in range(n):
if subset & (1 << i): # item i is in the subset
total = total + values[i]
x = a
y = b
while y != 0: # Euclid's algorithm
x, y = y, x % y
gcd = x
lcm = a // gcd * b # divide first, then multiply
answer = answer * current % m # reduce after every multiply
A sieve finds every prime up to N: for each prime p, mark p*p, p*p + p, … as not prime.
Binary trees · Unit 12
order = []
def preorder(node):
if node == -1: # -1 means no child
return
order.append(val[node]) # visit the node first
preorder(left[node])
preorder(right[node])
For in-order, visit the node between the two subtrees; for post-order, visit it after both. In a binary search tree, compare the target with the node and go one way only:
node = root
while node != -1 and val[node] != target:
if target < val[node]:
node = left[node]
else:
node = right[node]
Grids and graphs · Unit 13
dr = [-1, 1, 0, 0] # up, down, left, right
dc = [0, 0, -1, 1]
for direction in range(4):
nr = r + dr[direction]
nc = c + dc[direction]
if 0 <= nr and nr < rows and 0 <= nc and nc < cols:
print(grid[nr][nc])
adj = {}
for u, v in edges: # an undirected edge
if u not in adj:
adj[u] = []
if v not in adj:
adj[v] = []
adj[u].append(v)
adj[v].append(u)
Breadth-first search finds the fewest steps:
from collections import deque
queue = deque()
queue.append(start)
distance = {start: 0}
while len(queue) > 0:
current = queue.popleft() # the oldest first
for nxt in adj[current]:
if nxt not in distance:
distance[nxt] = distance[current] + 1
queue.append(nxt)
Depth-first search and flood fill mark what they visit:
def dfs(node, visited):
visited.add(node)
for neighbour in adj[node]:
if neighbour not in visited:
dfs(neighbour, visited)
Two pointers · Unit 14
lo = 0 # the list must be sorted
hi = len(values) - 1
found = False
while lo < hi and found == False:
total = values[lo] + values[hi]
if total == target:
found = True
elif total < target:
lo = lo + 1 # need a bigger sum
else:
hi = hi - 1 # need a smaller sum
A sliding window keeps a running sum; its values must not be negative:
left = 0
window_sum = 0
best = 0
for right in range(len(values)):
window_sum = window_sum + values[right]
while window_sum > limit and left <= right:
window_sum = window_sum - values[left]
left = left + 1
best = max(best, right - left + 1)