Here is the link to the contest. All problems are from Codeforces' problem set
- We are given a set $$$S = {l, l+1, \dots, r}$$$ and a positive integer $$$k$$$.
- We can repeatedly perform the operation:
- Pick $$$x \in S$$$ such that there are at least $$$k$$$ multiples of $$$x$$$ in $$$S$$$.
- Remove $$$x$$$ from $$$S$$$.
- The goal is to determine the maximum number of operations that can be performed.
Key Observation
Greedy removal from small to large:
- Once a number $$$x$$$ is removed, it cannot interfere with the removability of larger numbers.
- If $$$x \lt y$$$, then $$$x$$$ is never a multiple of $$$y$$$. Therefore, it is safe to consider numbers in increasing order.
Condition for removability:
- A number $$$x$$$ can be removed if there are at least $$$k$$$ multiples of $$$x$$$ in $$$S$$$.
- This can be simplified to the inequality:
- $$$k \cdot x \le r \quad \implies \quad x \le \left\lfloor \frac{r}{k} \right\rfloor$$$
- All numbers $$$x$$$ in the range $$$[l, \lfloor r/k \rfloor]$$$ can be removed. Numbers larger than this bound cannot satisfy the multiplicity requirement.
Approach 1: Constant-Time Computation
Compute the largest removable number:
- $$$x_{\max} = \left\lfloor \frac{r}{k} \right\rfloor$$$
Count all removable numbers:
- $$$\text{max_operations} = \max(x_{\max} - l + 1, 0)$$$
If $$$x_{\max} \lt l$$$, no numbers can be removed, resulting in 0 operations.
- Time complexity: $$$O(1)$$$ per test case, since it only requires basic arithmetic.
Approach 2: Binary Search
- Search space: all numbers $$$x \in [l, r]$$$.
- Condition check: for a candidate $$$x$$$, it is removable if:
- $$$k \cdot x \le r$$$
Binary search procedure:
- Perform a binary search to find the largest number $$$x_{\max}$$$ that satisfies the condition.
- The number of operations is then:
- Time complexity:
- Binary search requires $$$O(\log(r-l+1))$$$ operations per test case.
- Efficient for very large ranges of $$$l$$$ and $$$r$$$ where direct arithmetic might be less intuitive or when additional constraints are present.
Summary
Constant-Time Approach:
- Directly compute $$$\lfloor r/k \rfloor$$$ and count numbers from $$$l$$$ to this bound.
- Extremely fast and straightforward.
Binary Search Approach:
- Use when the condition may be more complex or when reasoning about the largest removable number iteratively is required.
- More flexible and generalizable, though slightly slower than the constant-time formula.
Both approaches rely on the key insight: removing numbers from smallest to largest ensures previously removed numbers do not affect future choices, and the removability condition can be reduced to $$$x \le \lfloor r/k \rfloor$$$.
def solve(l, r, k):
x_max = r // k
return max(x_max - l + 1, 0)
test = int(input())
for _ in range(test):
l, r, k = map(int, input().split())
print(solve(l, r, k))
def solve(l, r, k):
low, high = l, r
x_max = 0
while low <= high:
mid = (low + high) // 2
if mid * k <= r:
x_max = mid
low = mid + 1
else:
high = mid - 1
return max(x_max - l + 1, 0)
test = int(input())
for _ in range(test):
l, r, k = map(int, input().split())
print(solve(l, r, k))
Key Insight
- Treat consecutive
Es as groups of equal numbers. - Each group can be assigned a single number because all elements in that group are equal.
- The
Ns separate these groups.
Analysis of Ns
Let $$$c_N$$$ be the number of Ns in the string $$$s$$$.
No
N($$$c_N = 0$$$):- All elements are in a single
Egroup. - We can assign all elements the same value.
- Possible.
- All elements are in a single
Exactly one
N($$$c_N = 1$$$):- Only one pair of neighbors must differ.
- But since all other elements are connected by
Es, they must be equal to each other. - This creates a contradiction: that single
Nrequires two different numbers, but the rest are all equal. - Not possible.
More than one
N($$$c_N \ge 2$$$):- The
Ns divide the circle into multiple groups. - Alternate groups can be assigned different numbers to satisfy all
Ns. - Possible.
- The
Conclusion
- An array $$$a$$$ exists if and only if the number of
Ns is not equal to 1:
- This gives a very simple solution:
- Count the number of
Ns in $$$s$$$. - If it’s 1 → print
NO. - Otherwise → print
YES.
- Count the number of
Complexity
- Only needs counting
Ns → $$$O(n)$$$ per test case. - No need to assign actual values to array elements.
def solve(s):
n_count = 0
for char in s:
if char == "N":
n_count += 1
if n_count == 1:
return "NO"
else:
return "YES"
test = int(input())
for _ in range(test):
s = input()
print(solve(s))
Idea
We can solve the problem using Union-Find (Disjoint Set Union, DSU).
- Treat each index $$$i$$$ of the array as a node in a DSU.
- For every equality constraint $$$s_i = E$$$:
- $$$a_i = a_{i+1}$$$
- Union nodes $$$i$$$ and $$$i+1$$$.
- After processing all
Es, connected components represent numbers that must be equal. - For every inequality constraint $$$s_i = N$$$:
- $$$a_i \neq a_{i+1}$$$
- Check if $$$i$$$ and $$$i+1$$$ belong to the same component:
- If yes → contradiction → impossible.
- If no → constraint is satisfied.
- If all constraints are satisfied → array exists.
Advantages
- Handles generalized equality/inequality constraints.
- Tracks multiple equality groups dynamically.
Disadvantages
- Overkill for small $$$n$$$ and simple constraints.
Simpler approach: just count the number of
Ns:- If
count_N == 1→NO - Else →
YES
- If
Conclusion
- Union-Find can solve it.
- Counting
Ns is faster and simpler. - Use DSU if the problem has more complex or dynamic equality/inequality relations.
class UnionFind:
def __init__(self, n):
self.par = list(range(n))
self.rank = [0] * n
def find(self, x):
while self.par[x] != x:
self.par[x] = self.par[self.par[x]]
x = self.par[x]
return self.par[x]
def union(self, x, y):
x = self.find(x)
y = self.find(y)
if x == y:
return
if self.rank[x] < self.rank[y]:
self.par[x] = y
elif self.rank[x] > self.rank[y]:
self.par[y] = x
else:
self.par[y] = x
self.rank[x] += 1
test = int(input())
for _ in range(test):
s = input()
n = len(s)
union_find = UnionFind(n)
for i in range(n):
if s[i] == 'E':
union_find.union(i, (i+1)%n)
is_possible = True
for i in range(n):
if s[i] == 'N':
if union_find.find(i) == union_find.find((i+1)%n):
is_possible = False
break
print("YES" if is_possible else "NO")
- There are $$$n$$$ kids, each initially with a unique book.
- Each day, kid $$$i$$$ gives their book to kid $$$p[i]$$$.
- Goal: Determine for each kid the number of days until their book returns to them.
Key Observation
- The permutation $$$p$$$ forms disjoint cycles.
- A kid's book will return to them exactly after the length of the cycle containing that kid.
Union-Find Approach
Idea
- Treat each kid as a node in a graph.
- The relationship "kid $$$i$$$ gives book to kid $$$p[i]$$$" forms an edge.
- Each connected component (cycle) can be represented using Union-Find.
Steps
Initialization:
- Each kid is their own parent.
- Maintain a
sizearray to track the size of each cycle.
Union Operation:
- For each kid $$$i$$$, union $$$i$$$ with $$$p[i]$$$.
- After all unions, each cycle corresponds to a connected component in the Union-Find structure.
Cycle Size Calculation:
- For each kid $$$i$$$, the size of the component containing $$$i$$$ gives the number of days for the book to return.
- Use the
findoperation to get the root of $$$i$$$ and then look up its cycle size.
Summary
- Each book follows a fixed cycle determined by the permutation.
- Union-Find efficiently groups kids into cycles.
- The answer for each kid is simply the size of their cycle.
- This method works in nearly linear time, $$$O(n)$$$, using path compression for Union-Find.
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
self.size = [1] * n
def find(self, x):
while self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
x= self.parent[x]
return self.parent[x]
# union by size
def union(self, x, y):
px, py = self.find(x), self.find(y)
if px != py:
if self.size[px] < self.size[py]:
px, py = py, px
self.parent[py] = px
self.size[px] += self.size[py]
q = int(input())
for _ in range(q):
n = int(input())
arr = list(map(int, input().split()))
union_find = UnionFind(n)
for i in range(n):
union_find.union(i, arr[i] - 1)
ans = []
for node in range(n):
root = union_find.find(node)
ans.append(union_find.size[root])
print(*ans)
D. Loopix and the Government Payouts
We have:
- $$$n$$$ citizens with initial money $$$a_1, a_2, \dots, a_n$$$.
- $$$q$$$ events, each either:
1 p x→ citizen $$$p$$$ sets their balance to $$$x$$$ (receipt update)2 x→ government payout: all citizens with balance less than $$$x$$$ are raised to $$$x$$$.
We need to compute the final balance for each citizen after all events.
Key Observations
Only the last receipt matters for each citizen:
- Suppose a citizen has multiple type 1 events:
1 p x1, 1 p x2, 1 p x3 - Only the last one (
x3) matters, because it overwrites all previous updates. - Why it works: Any earlier update is irrelevant; the citizen’s balance will be overwritten by the last receipt before any final government payouts.
- Suppose a citizen has multiple type 1 events:
Government payouts act as a lower bound:
- Any type 2 event with value $$$x$$$ raises all citizens with less than $$$x$$$.
- Therefore, for each citizen, the final balance is:
- Why it works: The government payout can never reduce a citizen’s balance; it only enforces a minimum. So the final balance is constrained by the latest explicit receipt and the largest subsequent minimum imposed by type 2 events.
Step-by-Step Explanation
Step 1: Track the last type 1 query for each citizen
- Create an array
last_type1of size $$$n$$$. For every query
1 p x, set:- $$$\text{last_type1}[p] = x$$$
- After processing all type 1 queries,
last_type1[i]holds the latest explicit balance for citizen $$$i$$$.
Why this works: By keeping only the last receipt, we simplify calculations, ignoring intermediate receipts that will be overwritten.
Step 2: Track maximum type 2 queries for suffixes
- Government payouts can happen after a citizen’s last receipt update.
- Create an array
max_suffix_type2of size $$$q$$$. - Traverse the queries backwards (from last to first) and compute:
- $$$\text{max_suffix_type2}[i] = \max(\text{max_suffix_type2}[i+1], x)$$$ if query $$$i$$$ is type 2
- This ensures that for any query, we know the largest government payout that comes after it.
Why this works: Since only the largest type 2 after a citizen’s last receipt can affect their final balance, computing suffix maximums efficiently tracks these minimum constraints.
Step 3: Compute final balances
- For each citizen $$$i$$$, the final balance is:
- $$$\text{final_balance}[i] = \max(\text{last_type1}[i], \text{maximum type 2 after last type 1 for this citizen})$$$
- If a citizen never had a type 1 query, use their initial balance as
last_type1[i].
Why this works: Each citizen’s balance is the greater of the last explicit receipt and the largest government-enforced minimum after that receipt. This guarantees correctness without processing every citizen at each type 2 event.
Step 4: Complexity
Time complexity: $$$O(n + q)$$$
- Tracking last type 1 queries → $$$O(q)$$$
- Computing suffix maximums of type 2 → $$$O(q)$$$
- Computing final balances → $$$O(n)$$$
Space complexity: $$$O(n + q)$$$
last_type1array → $$$O(n)$$$max_suffix_type2array → $$$O(q)$$$
Intuition Recap
- Receipt updates overwrite previous values, so we only care about the last one per citizen.
- Government payouts provide a lower bound, only affecting citizens with balances below the payout.
- By combining these two:
we get the correct final balance in linear time, avoiding repeated updates for every citizen.
n = int(input())
arr = list(map(int, input().split()))
q = int(input())
queries = []
for _ in range(q):
curr_query = list(map(int, input().split()))
queries.append(curr_query)
last_type1 = arr[:]
max_type2_suffix = [0 for _ in range(q)]
max_val = 0
for i in range(q - 1, -1, -1):
if queries[i][0] == 2:
x = queries[i][1]
max_val = max(max_val, x)
max_type2_suffix[i] = max_val
last_type1_idx = [-1 for _ in range(n)]
for idx, query in enumerate(queries):
if query[0] == 1:
p = query[1] - 1
x = query[2]
last_type1[p] = x
last_type1_idx[p] = idx
ans = [0 for _ in range(n)]
for i in range(n):
idx = last_type1_idx[i]
max_type2_after = max_type2_suffix[idx + 1] if idx + 1 < q else 0
ans[i] = max(last_type1[i], max_type2_after)
print(*ans)
E. Loopix and the Desert Jumps
We want, for each tree position $$$i$$$, the maximum height that the rabbit can reach if it starts at tree $$$i$$$.
The key observation is that the rabbit’s jumping rules naturally group indices into connected components:
- If $$$i$$$ can jump to $$$j$$$, then $$$i$$$ and $$$j$$$ must be in the same component.
- Within a component, the rabbit can eventually reach the maximum height of that component.
Thus, the task reduces to: 1. Group indices into components. 2. Assign to each index the maximum height of its component.
We achieve this with Disjoint Set Union (DSU) and a monotonic stack.
Step 1. Why DSU?
- Each index belongs to exactly one connected component.
- If $$$i$$$ and $$$j$$$ are connected via the jumping rules, we merge them into the same component.
- DSU (Union-Find) is perfect for maintaining and merging such groups dynamically.
At the end, every component will have its maximum height, and each index will map to that value.
Step 2. Using a Monotonic Stack
We process the array from left to right, keeping a monotonic increasing stack (heights stored in increasing order).
At index $$$i$$$ with value $$$a[i]$$$:
- While the top of the stack has value greater than $$$a[i]$$$:
- Pop the stack element $$$j$$$.
- Merge $$$j$$$ and $$$i$$$ with DSU, since the forward jump rule is satisfied:
- During this popping, we keep track of the index $$$x$$$ with the maximum value inside the group.
- After processing, we push $$$x$$$ back onto the stack, ensuring the stack always represents the current group’s maximum.
This way, the stack efficiently determines which indices should be united.
Step 3. Building Components
As we iterate:
- Every time we pop from the stack, we
unionthe popped index with the current index. - After merging, the stack top always points to the representative index holding the largest value in the current component.
By the end of the pass:
- All reachable indices are united into components.
- Each component is correctly represented in DSU.
Step 4. Computing Maximum Heights
After building the DSU:
- For each root in the DSU, compute the maximum $$$a[i]$$$ in that component.
- For each index $$$i$$$, set:
Complexity Analysis
Time Complexity:
- Each index is pushed/popped from the stack at most once → $$$O(n)$$$.
- Each DSU
find/unionis $$$O(\alpha(n))$$$ (inverse Ackermann function, practically constant). - Total: $$$O(n)$$$.
Space Complexity:
- DSU arrays + stack + result array → $$$O(n)$$$.
Intuition Recap
- The monotonic stack enforces the rabbit’s forward-jump condition (smaller height to the right).
- The DSU groups indices into connected components as jumps are discovered.
- The maximum of each component gives the rabbit’s maximum reachable height.
- Finally, each index outputs the maximum of its component.
class UnionFind:
def __init__(self, n):
self.par = list(range(n))
self.rank = [0] * n
def find(self, x):
while self.par[x] != x:
self.par[x] = self.par[self.par[x]]
x = self.par[x]
return self.par[x]
def union(self, x, y):
x = self.find(x)
y = self.find(y)
if x == y:
return
if self.rank[x] < self.rank[y]:
self.par[x] = y
elif self.rank[x] > self.rank[y]:
self.par[y] = x
else:
self.par[y] = x
self.rank[x] += 1
def solve(arr):
n = len(arr)
union_find = UnionFind(n)
stack = []
for index in range(n):
last = stack[-1] if stack else index
while stack and arr[stack[-1]] > arr[index]:
union_find.union(stack.pop(), index)
if arr[last] > arr[index]:
stack.append(last)
else:
stack.append(index)
stack = []
for index in range(n -1, -1, -1):
last = stack[-1] if stack else index
while stack and arr[stack[-1]] < arr[index]:
union_find.union(stack.pop(), index)
if arr[last] < arr[index]:
stack.append(last)
else:
stack.append(index)
# For Every Componet Representative make the max value as there value
INF = 10**18
comp_max = [-INF for _ in range(n)]
for node in range(n):
root = union_find.find(node)
comp_max[root] = max(arr[node], comp_max[root])
ans = [ 0 for _ in range(n)]
for node in range(n):
root = union_find.find(node)
ans[node] = comp_max[root]
return ans
test = int(input())
for _ in range(test):
n = int(input())
arr = list(map(int, input().split()))
ans = solve(arr)
print(*ans)
For each starting index $$$i$$$ $$$(0 \leq i \lt n)$$$, determine the maximum tree height that the rabbit can eventually reach if it begins its sequence of jumps at tree $$$a[i]$$$.
What does "maximum reachable height" mean?
- The rabbit can jump backward only to a taller tree:
- The rabbit can jump forward only to a shorter tree:
- Thus, starting at $$$a[i]$$$, the rabbit may follow a chain of jumps:
and among all reachable positions, we want the tallest tree:
This is not a purely local property. jumps may allow the rabbit to step down on the right and then reach much taller trees on the left, so we need a global way to propagate reachability.
Step 1. Build Prefix Maximums
We compute:
- $$$\text{pref}[i]$$$ stores the tallest tree up to index $$$i$$$.
- Intuition: if the rabbit is at $$$i$$$, it may eventually reach some taller tree on the left. $$$\text{pref}[i]$$$ records that possibility.
Step 2. Build Suffix Minimums
We compute:
- $$$\text{suff}[i]$$$ stores the shortest tree from $$$i$$$ to the end.
- Intuition: since the rabbit can only jump forward to smaller trees, $$$\text{suff}[i]$$$ tells us how far it can step down on the right side.
Step 3. Propagation of Reachability
Now we compute the final answer array $$$\text{ans}$$$.
- Start at the last index:
- $$$\text{ans}[n-1] = \text{pref}[n-1]$$$(At the very end, the maximum reachable is just the tallest tree so far.)
- Move backward from $$$i = n-2$$$ down to $$$0$$$:
Case 1: If $$$\text{pref}[i] \gt \text{suff}[i+1]$$$
- There is a taller tree on the left of $$$i$$$.
- There is a smaller tree to the right of $$$i$$$.
- This means the rabbit at $$$i$$$ can chain through the right side and eventually reach the same maximum as starting at $$$i+1$$$.
- So we set: $$$\text{ans}[i] = \text{ans}[i+1]$$$
Case 2: Otherwise, the chain does not extend through the right side.
The best reachable height is just the tallest seen up to $$$i$$$:
Step 4. Final Result
- After finishing the backward pass, every index $$$i$$$ has been assigned the maximum tree height the rabbit can reach starting at $$$a[i]$$$.
This works because:
- $$$\text{pref}[i]$$$ ensures backward jumps are respected,
- $$$\text{suff}[i]$$$ ensures forward “step-downs” are considered,
- and the backward propagation of $$$\text{ans}$$$ connects reachable chains together.
Complexity Analysis
Time:
- Prefix array: $$$O(n)$$$
- Suffix array: $$$O(n)$$$
- Backward pass: $$$O(n)$$$
- Total: $$$O(n)$$$ per test case
Space:
- Arrays $$$\text{pref}$$$, $$$\text{suff}$$$, $$$\text{ans}$$$ → $$$O(n)$$$
Intuition Recap
- $$$\text{pref}[i] =$$$ tallest tree so far (handles backward jumps)
- $$$\text{suff}[i] =$$$ smallest tree to the right (handles forward jumps)
- If $$$\text{pref}[i] \gt \text{suff}[i+1]$$$, then $$$i$$$ connects into the same chain as $$$i+1$$$.
- Otherwise, the maximum reachable is just $$$\text{pref}[i]$$$.
def solve(arr):
n = len(arr)
pref_max = [0 for _ in range(n)]
suff_min = [0 for _ in range(n)]
pref_max[0] = arr[0]
for index in range(1, n):
pref_max[index] = max(arr[index], pref_max[index - 1])
suff_min[ n - 1] = arr[n - 1]
for index in range(n - 2, -1, -1):
suff_min[index] = min(arr[index], suff_min[index + 1])
ans = [0 for _ in range(n)]
ans[-1] = pref_max[-1]
for index in range(n - 2, -1, -1):
if pref_max[index] > suff_min[index + 1]:
ans[index] = ans[index + 1]
else:
ans[index] = pref_max[index]
return ans
test = int(input())
for _ in range(test):
n = int(input())
arr = list(map(int, input().split()))
ans = solve(arr)
print(*ans)







