Processing math: 100%
(Analysis by Benjamin Qi)

Subtask 1: M16:

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+M2M).

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: M1000

To remove the O(M2M) 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

  1. If there is a move sequence starting with seq+[0], then the answer will be found after calling find_best_move_seq_starting_with(seq+[0]).
  2. Otherwise, find_best_move_seq_starting_with(seq+[0]) immediately returns and the answer will be found after calling find_best_move_seq_starting_with(seq+[1]).

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:

  1. N+l1t=0changes[t][seq[t]]>0 for all l<|seq|.
  2. N+|seq|1t=0changes[t][seq[t]]+l1t=|seq|max(changes[t][0],changes[t][1])>0 for all |seq|lM.

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);
            }
        }
    }
}