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 longs.
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