Editorial for WC '15 Contest 2 J4 - The Force Awakens (Junior)
Submitting an official solution before solving the problem yourself is a bannable offence.
This is the last and most interesting problem of the junior contest, since it's the only problem which actually forces us to think about cutting down on the time complexity of our program. We are given a grid of empty cells, mirrors, and blocked cells. We must count the number of empty cells such that if we were to shoot a laser beam into at least one direction from that cell, the beam would eventually bounce back to the same cell.
Let's first consider some simple, yet inefficient solutions. The most straightforward solution is simulation. In other words, loop through all of the
- the row that the laser beam is current at,
- the column that the laser beam is current at, and
- the direction that the laser beam is currently travelling in.
The row and column can simply be stored as a pair of integers L
, R
, U
and D
) and check these values each time to make changes to the current position. However, a popular, more elegant method is just to keep a pair
Every time we encounter a mirror, we must change /
or \
mirror, the axis of the laser changes – i.e. horizontal beams become vertical, and vice versa. That means, we can simply swap whichever of /
mirror, we have to negate the non-zero value – rightward X
mirror will just reverse both components, so it's the exact same case as the check for the /
mirror and we can combine them into one. Altogether, the handling of changing directions can be done in two harmless-looking if-conditions:
if (g[r][c] == '/' || g[r][c] == '\\')
swap(dr, dc);
if (g[r][c] == '/' || g[r][c] == 'X') {
dr = -dr;
dc = -dc;
}
Note that in many programming languages, the backslash character \
is an escape character. To represent a single backslash, we'll need to escape it using yet another backslash. Finally, we may want to simplify checking of walls and barriers. If we completely pad our 2D character array with blocks (#
characters) before reading in the grid, and also read the grid into
Why is this solution not enough? Let's study its running time. If we have to simulate a laser beam from each of the
/\/\/\X
.......
.......
.\/\/\/
If we project the laser beam upwards from the bottom-left corner, it will travel to every other cell twice before returning to the original cell. This tells us that each simulation of a single laser also has the worst-case running time proportional to
The next step that one might consider is some optimization. As it turns out, there are a few ways we can go about optimizing the straight-simulation.
One way is by noticing that in the worst-case example we provided above, there exist many empty cells we pass through. It's not hard to see that we would be able to make our program much faster by simply "skipping over" those cells. In other words, we would like to make each "reflection" have
We can store a 3D array
While this is a good optimization, the solution is still not good enough. Consider the case below.
/......\
./....\.
../..\..
.../\...
...\./..
..\.../.
.\...../
\.../...
In a test case like this, a laser travelling leftwards from the
There are other optimizations we can do, such as noticing whenever a cycle has been reached, all cells in the cycle (going in the correct direction) must also be deadly. However, as long as we refuse to abandon the idea of projecting lasers outwards of each empty square, it all seems futile. Unless we can optimize the time of each simulation to a whole order of complexity less than
So here's a crazy idea. How about we simulate from the blocked squares instead?
Turns out, it's not so crazy at all. Consider this "obvious" observation – every possible laser beam in the grid (at some coordinate of an empty cell, travelling in some direction) has exactly two possible fates:
- It will end up back in the same coordinates, travelling in the same direction. We've already established that this means every cell in the cycle is considered deadly.
- It will hit a wall or blocked square.
We know that any laser beam's path is "deterministic," in the sense that if we were to shoot a laser-beam from a given cell which eventually ends up hitting a wall/block in a particular direction, then shooting the beam in reverse from the coordinates of that wall/block will eventually pass through the original cell.
Much more importantly, it is guaranteed that a laser beam coming out of a wall/block will never be able to enter into an infinite cycle. In other words, the laser beam will always end up at yet another wall/block. Convince yourself that this is true by trying to come up with counterexamples (you won't be able to). This presents the following approach:
First consider each neighbouring cell of a wall/block which is not blocked (either an empty cell or a mirror). We will simulate a laser coming out of that wall/block in the direction of the non-blocked cell. For each of the simulations, we keep track of which cells in the grid we've encountered in that particular simulation only. If we notice that a cell has already been visited in the same simulation, then it must be deadly.
How? We could keep a 2D boolean array of all the cells visited in the current simulation, but resetting it each time is too slow and will still make the running time quadratic. Instead, we can number each simulation (just count upwards from
However, we may still not be counting the deadly cells which are infinitely involved in a cycle (and never hits a wall). Let's consider each of the
So, we can keep yet another boolean array
Note that since wall/block laser beams are deterministic, it is unnecessary to follow a beam going in the same direction at a given cell more than once. Therefore, we notice that if
Comments