Here is the contest link
A. PolandBall and Hypothesis
Idea and preparation:
Since $$$m$$$ only needs to be between $$$1$$$ and $$$1000$$$, can we just guess values for $$$m$$$ and check if $$$n \cdot m + 1$$$ is composite? Alternatively, is there an algebraic way to force the expression to become a perfect square?
There are two highly effective ways to solve this problem:
Approach 1: Brute Force Since the constraints are extremely small ($$$n \le 1000$$$ and we only need $$$m \le 1000$$$), the maximum number we will ever check is $$$1000 \times 1000 + 1 \approx 10^6$$$. We can simply loop $$$m$$$ from 1 to 1000, calculate $$$val = n \cdot m + 1$$$, and check if $$$val$$$ is prime in $$$O(\sqrt{val})$$$ time. The first $$$m$$$ that yields a composite number is our answer.
Approach 2: $$$O(1)$$$ Math (Forcing a Square) If we can make $$$n \cdot m + 1$$$ equal to a perfect square like $$$(n-1)^2$$$, it will definitely be composite. $$$(n - 1)^2 = n^2 - 2n + 1$$$ Setting them equal: $$$n \cdot m + 1 = n^2 - 2n + 1 \implies n \cdot m = n^2 - 2n \implies m = n - 2$$$. So, for any $$$n \gt 2$$$, $$$m = n - 2$$$ is a guaranteed $$$O(1)$$$ answer. For the special cases $$$n=1$$$ and $$$n=2$$$, we can just output $$$m=3$$$ and $$$m=4$$$ respectively.
- Time Complexity: $$$O(m \sqrt{nm})$$$ for brute force, or $$$O(1)$$$ for math.
- Space Complexity: $$$O(1)$$$
import sys
def is_prime(x):
if x <= 1:
return False
i = 2
while i * i <= x:
if x % i == 0:
return False
i += 1
return True
def solve():
n = int(sys.stdin.read().strip())
# Approach 1: Brute Force
for m in range(1, 1001):
if not is_prime(n * m + 1):
print(m)
break
# Approach 2 (O(1) Math Alternative):
# if n == 1: print(3)
# elif n == 2: print(4)
# else: print(n - 2)
if __name__ == "__main__":
solve()
B. Rook, Bishop and King
Idea and preparation:
You can solve this using pure coordinate geometry based on how chess pieces move, or you can model the chessboard as an unweighted graph and use Breadth-First Search (BFS) to find the shortest path layer by layer.
Approach 1: O(1) Geometry * Rook: Moves in straight lines. If it shares a row or column with the target, 1 move. Otherwise, 2 moves. * Bishop: Moves diagonally. Squares on the same diagonal share either $$$r+c$$$ or $$$r-c$$$. If parities $$$(r1+c1) \pmod 2 \neq (r2+c2) \pmod 2$$$, the target is unreachable (0). If it's on the exact same diagonal, 1 move. Otherwise, 2 moves. * King: Can move in all 8 directions. The shortest path is the Chebyshev distance: $$$\max(|r1-r2|, |c1-c2|)$$$.
Approach 2: Shortest Path (BFS) Since a chessboard is a small $$$8 \times 8$$$ grid, we can represent it as an unweighted graph where edges are the legal moves of a piece. By pushing the starting coordinate into a queue and exploring level-by-level, BFS mathematically guarantees that the first time we hit the target square, we have found the absolute minimum number of moves. We just define three different "move generators" (neighbors) for each piece and run standard BFS.
- Time Complexity: $$$O(1)$$$ for math, $$$O(V+E)$$$ (which is at most 64) for BFS.
- Space Complexity: $$$O(1)$$$ for math, $$$O(V)$$$ for BFS queue and visited set.
from collections import deque
import sys
def bfs(start, target, get_moves):
if start == target:
return 0
queue = deque([(start[0], start[1], 0)])
visited = {start}
while queue:
r, c, dist = queue.popleft()
if (r, c) == target:
return dist
for nr, nc in get_moves(r, c):
if (nr, nc) not in visited:
visited.add((nr, nc))
queue.append((nr, nc, dist + 1))
return 0
def solve():
r1, c1, r2, c2 = map(int, sys.stdin.read().split())
start, target = (r1, c1), (r2, c2)
# Move generators
def rook_moves(r, c):
return [(r, i) for i in range(1, 9) if i != c] + [(i, c) for i in range(1, 9) if i != r]
def bishop_moves(r, c):
moves = []
for dr, dc in [(-1, -1), (-1, 1), (1, -1), (1, 1)]:
nr, nc = r, c
while 1 <= nr + dr <= 8 and 1 <= nc + dc <= 8:
nr += dr; nc += dc
moves.append((nr, nc))
return moves
def king_moves(r, c):
dirs = [(-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)]
return [(r+dr, c+dc) for dr, dc in dirs if 1 <= r+dr <= 8 and 1 <= c+dc <= 8]
print(bfs(start, target, rook_moves), bfs(start, target, bishop_moves), bfs(start, target, king_moves))
if __name__ == "__main__":
solve()
C. Students and Shoelaces
Idea and preparation:
Think of the students as nodes and shoelaces as edges. We are looking for "leaves" (nodes with exactly 1 edge). How can we simulate removing all leaves simultaneously, layer by layer?
This is a graph simulation problem that introduces the concept of graph peeling.
We can store the graph using an array of sets to allow for $$$O(1)$$$ removal of edges. At each iteration (representing a reprimand phase): 1. Scan all nodes to find those with exactly 1 connection (len(adj[i]) == 1). 2. If we find no such nodes, the process stops. 3. If we find them, store them in a temporary list. We must gather them all first before kicking them out so we don't accidentally remove a chain sequentially in one step. 4. Iterate through our temporary list, remove the student from their neighbor's set, and clear the student's set. 5. Increment our groups kicked counter and repeat.
- Time Complexity: $$$O(n^3)$$$ worst-case if using lists, but $$$O(n^2)$$$ using sets to remove edges. Given $$$n \le 100$$$, this is incredibly fast.
- Space Complexity: $$$O(n + m)$$$ to store the graph.
from collections import defaultdict
n, m = map(int, input().split())
graph = defaultdict(list)
degree = [0] * n
for _ in range(m):
u, v = map(int, input().split())
u, v = u - 1, v - 1
graph[u].append(v)
graph[v].append(u)
degree[u] += 1
degree[v] += 1
removed = [False] * n
cnt = 0
leaves = [i for i in range(n) if degree[i] == 1 and not removed[i]]
while leaves:
cnt += 1
for node in leaves:
removed[node] = True
for node in leaves:
for neighbor in graph[node]:
degree[neighbor] -= 1
leaves = [i for i in range(n) if degree[i] == 1 and not removed[i]]
print(cnt)
D. Gardener and Tree
Idea and preparation:
Because $$$k$$$ can be up to $$$2 \cdot 10^5$$$, peeling the graph manually like in the previous problem will cause a Time Limit Exceeded (TLE) error. How can we use a queue to track the "peeling layers" efficiently, similar to Topological Sorting?
This requires Multi-source BFS (a variation of Kahn's Algorithm for Topological Sort). Instead of searching the whole graph repeatedly, we push the work strictly inward from the leaves.
- Initialize: Create an array
remto track the degree of each node. Find all initial leaves (rem[v] <= 1) and push them into a queue. Record their "deletion layer" as 1 in alayerarray. - Process: While the queue is not empty, pop a node $$$u$$$. For every neighbor $$$v$$$, simulate the removal of $$$u$$$ by decreasing
rem[v]by 1. - Queue new leaves: If $$$v$$$'s remaining degree drops exactly to 1, it has just become a leaf! Its deletion layer will be
layer[v] = layer[u] + 1. Push $$$v$$$ into the queue. - Count Answer: After the queue is empty, the graph has been fully peeled. The surviving nodes are simply those where
layer[v] > k.
- Time Complexity: $$$O(V + E)$$$ where $$$V = n$$$ and $$$E = n - 1$$$. Each node and edge is processed at most twice.
- Space Complexity: $$$O(V)$$$ for adjacency list, queue, and degree/layer arrays.
import sys
from collections import deque
def solve():
input_data = sys.stdin.read().split()
if not input_data:
return
t = int(input_data[0])
ptr = 1
out = []
for _ in range(t):
n = int(input_data[ptr])
k = int(input_data[ptr+1])
ptr += 2
if n == 1:
# Special case: single node is removed on layer 1
out.append("0" if k >= 1 else "1")
continue
adj = [[] for _ in range(n + 1)]
rem = [0] * (n + 1)
layer = [0] * (n + 1)
for _ in range(n - 1):
u, v = int(input_data[ptr]), int(input_data[ptr+1])
adj[u].append(v)
adj[v].append(u)
rem[u] += 1
rem[v] += 1
ptr += 2
queue = deque()
for i in range(1, n + 1):
if rem[i] == 1:
queue.append(i)
layer[i] = 1
while queue:
u = queue.popleft()
for v in adj[u]:
if rem[v] != 1:
rem[v] -= 1
if rem[v] == 1:
layer[v] = layer[u] + 1
queue.append(v)
ans = sum(1 for i in range(1, n + 1) if layer[i] > k)
out.append(str(ans))
print("\n".join(out))
if __name__ == "__main__":
solve()
E. Peaceful Rooks
Idea and preparation:
Consider rooks as directed edges in a graph where an edge goes from the rook's column to its row ($$$x \to y$$$). How does the presence of cycles in this graph affect the number of moves needed to place them on the main diagonal?
Because no rooks share a row or column, every node in our graph has an in-degree of at most 1 and an out-degree of at most 1. This means the graph decomposes perfectly into disjoint paths, cycles, and loops.
Moving a rook to the diagonal $$$(v \to v)$$$ is equivalent to turning its edge into a self-loop. * Loops: Already on the diagonal. Cost = 0 moves. * Paths: We can resolve them starting from the endpoint without conflicts. A path of $$$k$$$ rooks takes exactly $$$k$$$ moves. * Cycles: To resolve a cycle, we must temporarily move one rook off its intended target into an empty row/col to unblock the others. This breaks the cycle into a path, wasting exactly 1 extra move. A cycle of $$$k$$$ rooks takes $$$k + 1$$$ moves.
Therefore, the total optimal moves is: Total Rooks $$$-$$$ (Rooks already on diagonal) $$$+$$$ (Number of Cycles)
We can count cycles easily using a 3-state graph traversal (0: unvisited, 1: visiting, 2: processed).
- Time Complexity: $$$O(n)$$$. Each node is visited and backtracked exactly once.
- Space Complexity: $$$O(n)$$$ for the adjacency array and visited states.
import sys
# Increase recursion depth just in case, though iterative is used below
sys.setrecursionlimit(200000)
def solve():
input_data = sys.stdin.read().split()
if not input_data:
return
t = int(input_data[0])
ptr = 1
out = []
for _ in range(t):
n = int(input_data[ptr])
m = int(input_data[ptr+1])
ptr += 2
adj = [0] * (n + 1)
moves = 0
for _ in range(m):
u, v = int(input_data[ptr]), int(input_data[ptr+1])
ptr += 2
if u == v:
continue
adj[u] = v
moves += 1
vis = [0] * (n + 1)
cycles = 0
for i in range(1, n + 1):
if adj[i] != 0 and vis[i] == 0:
curr = i
# Traverse current path
while curr != 0 and vis[curr] == 0:
vis[curr] = 1
curr = adj[curr]
# Cycle detected
if curr != 0 and vis[curr] == 1:
cycles += 1
# Backtrack and mark as processed (State 2)
curr = i
while curr != 0 and vis[curr] == 1:
vis[curr] = 2
curr = adj[curr]
out.append(str(moves + cycles))
print("\n".join(out))
if __name__ == "__main__":
solve()







