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