Here is the link to the contest (the problems are from Codeforces problemset).
A. Distribute
Because of it is guaranteed, that the answer always exists, we need to sort all cards in non-descending order of the numbers, which are written on them. Then we need to give to the first player the first and the last card from the sorted array, give to the second player the second and the penultimate cards and so on.
from sys import stdin
def input(): return stdin.readline().strip()
N = int(input())
a = list(map(int, input().split()))
nums_idx = [(a[i], i) for i in range(N)]
nums_idx.sort()
for i in range(N//2):
i1 = nums_idx[i][1] + 1
i2 = nums_idx[N - 1 - i][1] + 1
print(i1, i2)
B. Grid Path
By simulating the movements, we can solve the problem using DP. where
- $$$dp[1][1] = 0$$$, and
- $$$dp[row][col] = min(dp[row - 1][col] + col, dp[row][col - 1] + row)$$$
import sys
input = lambda: sys.stdin.readline().rstrip()
t = int(input())
for _ in range(t):
n, m, k = map(int, input().split())
dp = [[0]*(m + 1) for row in range(n + 1)]
dp[1][1] = 0
for row in range(1, n + 1):
for col in range(1, m + 1):
if (row, col) == (1, 1):
continue
dp[row][col] = min(dp[row - 1][col] + col, dp[row][col - 1] + row)
print('YES' if dp[n][m] == k else 'NO')
We are doing unnecessary work. Can we avoid that?
Note that whichever path you choose, the total cost will be the same. If you know that the cost is the same, then it's not hard to calculate it. It's equal to $$$n⋅m−1$$$. So the task is to check: is $$$k$$$ equal to $$$n⋅m−1$$$ or not. The constant cost may be proved by induction on $$$n+m$$$:
- For $$$n=m=1$$$ the cost is $$$1⋅1−1=0$$$.
-
- For a fixed $$$(n,m)$$$, there are only two last steps you can make: either
- from $$$(n,m−1)$$$ with cost $$$n$$$: the total cost is $$$n⋅(m−1)−1+n = n⋅m−1$$$ or
- from $$$(n−1,m)$$$ with cost $$$m$$$: the total cost is $$$(n−1)⋅m−1+m = n⋅m−1$$$.
import sys
input = lambda: sys.stdin.readline().rstrip()
t = int(input())
for _ in range(t):
n, m, k = map(int, input().split())
print('YES' if n*m - 1 == k else 'NO')
C. Palindromic Sum Representations
Strategy
- Generate Palindromic Numbers: First, identify all palindromic numbers within the range 0 to 40,000. This involves checking each number in this range to see if it reads the same backward as forward.
- Relate to Coin Change Problem: Once we have the palindromic numbers, we can frame our problem similarly to the "coin change" problem. In the coin change problem, you are given a set of coins and you need to find the number of ways to make a certain amount using those coins.
Dynamic Programming Approach
- Initialize: We create an array
dp
where each indexi
represents an amount, and the value at each index represents the number of ways to make that amount using the palindromic numbers. - Base Case: We start with the base case that there is exactly one way to make the amount zero, which is by using no coins at all. Hence,
dp[0] = 1
. - Iterate Over Palindromic Numbers: For each palindromic number
c
, we update ourdp
array using the recurrence relation.
Recurrence Relation
The recurrence relation used to update the dp
array is:
dp[i] += dp[i — c]
This means that for each amount i
, the number of ways to form i
is incremented by the number of ways to form i — c
.
def generate_palindromic_numbers():
coins = []
for i in range(1, 4 * 10000 + 4):
num = str(i)
if num == num[::-1]:
coins.append(i)
return coins
coins = generate_palindromic_numbers()
max_num = 4 * 10 ** 4 + 2
mod = 10 ** 9 + 7
dp = [0] * (max_num)
dp[0] = 1
#compute outside because we don’t really have to compute it again and again
for coin in coins:
for i in range(max_num):
if i - coin >= 0:
dp[i] += dp[i - coin]
dp[i] %= mod
#for each test case output dp[n]
t = int(input())
for _ in range(t):
n = int(input())
print(dp[n])
D. Longest Good Sequence
The main idea is $$$DP$$$. Let's define $$$dp[x]$$$ as the maximal value of the length of the good sequence whose last element is $$$x$$$, and define $$$d[i]$$$ as the (maximal value of $$$dp[x]$$$ where $$$x$$$ is divisible by $$$i$$$).
You should calculate $$$dp[x]$$$ in the increasing order of $$$x$$$. The value of $$$dp[x]$$$ is (maximal value of $$$d[i]$$$ where $$$i$$$ is a divisor of $$$x$$$) + $$$1$$$. After you calculate $$$dp[x]$$$, for each divisor $$$i$$$ of $$$x$$$, you should update $$$d[i]$$$ too.
Note that there is a corner case. When the set is $$${1}$$$, you should output $$$1$$$.
import sys
n = int(sys.stdin.readline().strip())
nums = list(map(int, sys.stdin.readline().strip().split()))
if n == 1 and nums[0] == 1:
print(1)
sys.exit()
def find_divisors(number):
divisors = []
for i in range(2, int(number**0.5) + 1):
if number % i == 0:
divisors.append(i)
if i != number // i:
divisors.append(number // i)
return divisors
dp = [0] *(max(nums) + 1)
for num in nums:
divisors = find_divisors(num)
dp[num] = 1
for div in divisors:
dp[num] = max(dp[num], dp[div] + 1)
for div in divisors:
dp[div] = max(dp[div], dp[num])
print(max(dp))
E. Reachable Arrays
Let's rephrase the problem a bit. Instead of counting the number of arrays, let's count the number of subsequences of elements that can remain at the end.
One of the classic methods for counting subsequences is dynamic programming of the following kind: $$$\text{dp}_i$$$ is the number of subsequences such that the last taken element is at position $$$i$$$. Counting exactly this is that simple. It happens that element $$$i$$$ cannot be the last overall because it is impossible to remove everything after it. Thus, let's say it a little differently: the number of good subsequences on a prefix of length $$$i$$$. Now we are not concerned with what comes after $$$i$$$ — after the prefix.
If we learned to calculate such $$$\text{dp}$$$, we could get the answer from it. We just need to understand when we can remove all elements after a fixed one. It is easy to see that it is enough for all these elements to be smaller than the fixed one. Then they can be removed one by one from left to right. If there is at least one larger element, then the maximum of such elements cannot be removed.
Therefore, the answer is equal to the sum of $$$\text{dp}$$$ for all $$$i$$$ such that $$$a_i$$$ is the largest element on a suffix of length $$$n-i+1$$$.
How to calculate such $$$\text{dp}$$$?
Let's look at the nearest element to the left that is larger than $$$a_i$$$. Let its position be $$$j$$$. Then any subsequence that ended with an element between $$$j$$$ and $$$i$$$ can have the element $$$i$$$ appended to it. It is only necessary to remove the elements standing before $$$i$$$. This can also be done one by one.
Can $$$j$$$ be removed as well? It can, but it's not that simple. Only the element that is the nearest larger one to the left for $$$a_j$$$ or someone else even more to the left can do this. Let $$$f[i]$$$ be the position of the nearest larger element to the left. Then the element $$$i$$$ can also extend subsequences ending in $$$a[f[i]], a[f[f[i]]], a[f[f[f[i]]]], \ldots$$$.
If there are no larger elements to the left of the element — the element is the maximum on the prefix — then $$$1$$$ is added to its $$$\text{dp}$$$ value. This is a subsequence consisting of only this element.
The position of the nearest larger element to the left can be found using a monotonic stack: keep a stack of elements; while the element at the top is smaller, remove it; then add the current one to the stack.
Counting the $$$\text{dp}$$$ currently works in $$$O(n^2)$$$ in the worst case. How to optimize this? The first type of transitions is optimized by prefix sums, as it is simply the sum of $$$\text{dp}$$$ on a segment. For the second type of transitions, you can maintain the sum of $$$\text{dp}$$$ on a chain of jumps to the nearest larger element: $$$\text{dpsum}_i = \text{dpsum}_{f[i]} + \text{dp}_i$$$. Now, both transitions can be done in $$$O(1)$$$.
Overall complexity: $$$O(n)$$$ for each testcase.
MOD = 998244353
def solve():
n = int(input())
a = list(map(int, input().split()))
next_min = [0] * n
dp_sum = [0] * (n + 2)
dp_next = [0] * n
stack_min = []
next_min[n - 1] = n
dp_sum[n] = 1
for pos in range(n - 1, -1, -1):
while stack_min and a[stack_min[-1]] > a[pos]:
stack_min.pop()
next_min[pos] = n if not stack_min else stack_min[-1]
stack_min.append(pos)
nxt_pos = next_min[pos]
dp_pos = (dp_sum[pos + 1] - dp_sum[nxt_pos + 1]) % MOD
if nxt_pos != n:
dp_pos = (dp_pos + dp_next[nxt_pos]) % MOD
dp_next[pos] = (dp_sum[nxt_pos] - dp_sum[nxt_pos + 1] + dp_next[nxt_pos]) % MOD
dp_sum[pos] = (dp_pos + dp_sum[pos + 1]) % MOD
res = 0
mn = a[0]
for i in range(n):
mn = min(mn, a[i])
if a[i] == mn:
res = (res + dp_sum[i] - dp_sum[i + 1]) % MOD
print(res)
t = int(input())
for _ in range(t):
solve()