Subtask 1: M≤16:
For this subtask, it suffices to iterate through all of Elsie's 2M candidate turn sequences in lexicographical order. If with Elsie's current candidate turn sequence, she is guaranteed to not lose, output the current sequence, and exit.
It remains to describe how to check whether Elsie is guaranteed to not lose with a given candidate turn sequence. Given Elsie's guess p for a turn t, the best response for Bessie is the one that causes Elsie to lose the most marbles on that turn (or gain the least marbles if none of Bessie's responses can cause Elsie to lose marbles on that turn). Define changes[t][p] to be the amount N will change on turn t if Elsie guesses parity p and Bessie moves optimally. After precomputing all entries of changes in O(MK) time, we can check whether a given turn sequence causes Elsie to not lose in O(M) time.
The overall time complexity is O(MK+M⋅2M).
Implementation (Python):
def solve(): N, M, K = map(int, input().split()) # changes[t][p] = how many marbles Elsie will gain on turn t, # if Elsie guesses parity p and Bessie responds optimally changes = [] for _ in range(M): change_for_turn = [float("inf")] * 2 for a in map(int, input().split()): parity = a & 1 change_for_turn[parity] = min(change_for_turn[parity], a) change_for_turn[parity ^ 1] = min(change_for_turn[parity ^ 1], -a) changes.append(change_for_turn) ans = None def elsie_does_not_lose(seq): state = N for turn in range(M): state += changes[turn][seq[turn]] if state <= 0: return False return True def find_best_move_seq_starting_with(seq): nonlocal ans if ans is not None: return if len(seq) == M: if elsie_does_not_lose(seq): ans = seq return for parity in range(2): find_best_move_seq_starting_with(seq + [parity]) find_best_move_seq_starting_with([]) if ans is None: print(-1) return print(" ".join([["Even", "Odd"][p] for p in ans])) T = int(input()) for _ in range(T): solve()
Subtask 2: M≤1000
To remove the O(M⋅2M) from the time complexity, we can make the following optimization to the function find_best_move_seq_starting_with(seq) from the above solution code: if there is no sequence of moves starting with seq that allows Elsie to not lose, then immediately return. To check whether such a sequence exists or not, we assume that for every turn after |seq|, Elsie guesses the parity that causes her to gain the most marbles, assuming Bessie responds optimally. If this allows Elsie to not lose, then a sequence exists. Otherwise, no sequence exists, since Elsie lost despite always making the best move after seq. This can be checked in O(M) time.
After adding the optimization, the function find_best_move_seq_starting_with is called O(M) times rather than O(2M) times, because if find_best_move_seq_starting_with(seq) does not immediately return, then
The overall time complexity is O(MK+M2).
Implementation with recursion:
import sys def solve(): N, M, K = map(int, input().split()) sys.setrecursionlimit(M + 5) changes = [] for _ in range(M): change_for_turn = [float("inf")] * 2 for a in map(int, input().split()): parity = a & 1 change_for_turn[parity] = min(change_for_turn[parity], a) change_for_turn[parity ^ 1] = min(change_for_turn[parity ^ 1], -a) changes.append(change_for_turn) ans = None def cannot_start_with(seq): state = N for turn in range(M): if turn < len(seq): state += changes[turn][seq[turn]] else: state += max(changes[turn]) if state <= 0: return True return False def find_best_move_seq_starting_with(seq): nonlocal ans if ans is not None: return if cannot_start_with(seq): return if len(seq) == M: ans = seq return for parity in range(2): find_best_move_seq_starting_with(seq + [parity]) find_best_move_seq_starting_with([]) if ans is None: print(-1) return print(" ".join([["Even", "Odd"][p] for p in ans])) T = int(input()) for _ in range(T): solve()
Implementation without recursion:
def solve(): N, M, K = map(int, input().split()) changes = [] for _ in range(M): change_for_turn = [float("inf")] * 2 for a in map(int, input().split()): parity = a & 1 change_for_turn[parity] = min(change_for_turn[parity], a) change_for_turn[parity ^ 1] = min(change_for_turn[parity ^ 1], -a) changes.append(change_for_turn) ans = None def cannot_start_with(seq): state = N for turn in range(M): if turn < len(seq): state += changes[turn][seq[turn]] else: state += max(changes[turn]) if state <= 0: return True return False if cannot_start_with([]): print(-1) return ans = [] for _ in range(M): ans.append(1 if cannot_start_with(ans + [0]) else 0) print(" ".join([["Even", "Odd"][p] for p in ans])) T = int(input()) for _ in range(T): solve()
Full solution:
To reduce O(M2) to O(M) time, we need to speed up the function cannot_start_with(seq) from the above solution code. This function checks the following two conditions:
Suppose that the first condition already holds; then the second condition holds if and only if the minimum prefix sum (including the empty prefix) of the array
[max(changes[t]) for t in range(len(seq), M)]
is greater than some threshold. Let min_psum[t] denote the minimum prefix sum when |seq|=t; then we have min_psum[t]=min(0,max(changes[t])+min_psum[t+1]) for t<M. So min_psum[t] can be computed for all t in reverse order in O(M) time.
The full solution is now clear; it is the same as the non-recursive solution to the previous subtask but replacing the call to cannot_start_with(seq) with a comparison involving the current value of N and min_psum.
The overall time complexity is O(MK).
My Implementation:
def solve(): N, M, K = map(int, input().split()) changes = [] for _ in range(M): change_for_turn = [float("inf")] * 2 for a in map(int, input().split()): parity = a & 1 change_for_turn[parity] = min(change_for_turn[parity], a) change_for_turn[parity ^ 1] = min(change_for_turn[parity ^ 1], -a) changes.append(change_for_turn) min_psum = [0] * (M + 1) for t in reversed(range(M)): min_psum[t] = min(0, max(changes[t]) + min_psum[t + 1]) if N + min_psum[0] <= 0: print(-1) return ans = [] for t in range(M): p = 1 if (N + changes[t][0] + min_psum[t + 1] <= 0) else 0 ans.append(p) N += changes[t][p] print(" ".join([["Even", "Odd"][p] for p in ans])) T = int(input()) for _ in range(T): solve()
Danny Mittal's Implementation (Java):
import java.util.Arrays; import java.util.Scanner; import java.util.StringJoiner; public class Moorbles { public static void main(String[] args) { Scanner in = new Scanner(System.in); for (int t = in.nextInt(); t > 0; t--) { int n = in.nextInt(); int m = in.nextInt(); int k = in.nextInt(); int[][] deltas = new int[m][2]; for (int j = 0; j < m; j++) { Arrays.fill(deltas[j], 10000); for (int u = k; u > 0; u--) { int x = in.nextInt(); deltas[j][x % 2] = Math.min(deltas[j][x % 2], x); deltas[j][(x + 1) % 2] = Math.min(deltas[j][(x + 1) % 2], -x); } } int[] bounds = new int[m + 1]; for (int j = m - 1; j >= 0; j--) { bounds[j] = Math.max(0, bounds[j + 1] - Math.max(deltas[j][0], deltas[j][1])); } if (n <= bounds[0]) { System.out.println(-1); } else { StringJoiner joiner = new StringJoiner(" "); for (int j = 0; j < m; j++) { if (n + deltas[j][0] > bounds[j + 1]) { n += deltas[j][0]; joiner.add("Even"); } else { n += deltas[j][1]; joiner.add("Odd"); } } System.out.println(joiner); } } } }