Editorial for Baltic OI '19 P2 - Nautilus
Submitting an official solution before solving the problem yourself is a bannable offence.
Subtask 1
(; there are no
?
queries)
The solution to this subtask is straightforward — try all possible starting positions. It is simple to verify whether the ship could have started at a given position: simply simulate the ship's movement, starting from that position. If you hit an edge or island, you know that the ship couldn't have started there.
Verifying this for each position takes time. There are
possible starting positions: total time complexity will be
.
Subtask 2
()
We solve this using dynamic programming. We want to be
if and only if it is possible that the ship is in position
after the first
steps, considering only the first
characters of
.
We can make some observations. If there is an island at position , then
will be
, so let's look at the case where there is no island at position
. It's clear that
for any
and
. If the message at position
is a letter, for example
E
, then , because if the ship indeed was at position
at time
and went east before that, then it must have been at
at time
. If the message at position
is a question mark, then
is
if and only if at least one of
,
,
,
is
, because the ship must have come from one of these squares.
Using these relations, we can calculate for all
and
. The answer is the number of pairs
such that
.
The complexity is once again .
Subtask 3
()
The complexity shall still be , but we are going to use a very powerful way to optimize: bitsets! In C++,
bitset
is a data type that can be thought of as an array of booleans that supports, among other things, the following operations: bitwise "or" (denoted ); bitwise "and" (denoted
) and the left and right bitwise shifts (denoted
and
respectively). Bitwise "and" and "or" are simply the logical "and" and "or" operations, applied to each bit separately. For example:
The bitwise shifts shift the whole string by an integer number of positions, appending or prepending zeroes if necessary. For example: ;
. Compared to manually doing those operations on an array of booleans, they are very fast, because there exist machine-level instructions for doing them.
We represent as a bitset, where
consists of what
were in the last subtask. Also let
be a bitset of the
row of the grid – where
.
is converted to and
#
is converted to . Now notice that we can do the entire computation in bitsets:
Cases S
and W
are handled analogously.
How to do this in Java or Python? In Python, the jury simply used Python's big integers and treated them as bitstrings. In Java, both the BigInteger
and BitSet
classes exist, but neither work for this problem: BigInteger
is too slow and BitSet
lacks the bit-shift operators that we require. Instead, we replaced each bitset with a short array of long
s.
In fact, there are other ways to optimize the C++ solution. For example including the following line:
#pragma GCC optimize("Ofast")
will include optimizations hardcore enough to be comparable with bitsets.
Credits
- Task: Marijonas Petrauskas (Lithuania)
- Solutions and tests: Tähvend Uustalu, Andres Unt (Estonia)
Comments