Editorial for Baltic OI '19 P3 - Alpine Valley
Submitting an official solution before solving the problem yourself is a bannable offence.
Subtask 1
(
Removing an edge escaped
. Otherwise, it is fairly straightforward to iterate over all shop vertices on the "side" of
Subtask 2
(
The simplest solution (and typically, insufficient) to any problem is to simply do what the problem statement tells us to do, without giving it any more thought. This subtask can be solved by doing exactly that.
Suppose a query
- traverse the graph (pretending that the edge
does not exist) to calculate, for each vertex , the distance from to ; - if the distance from
to is not , announce that it is possible to reach from ; - otherwise, iterate over all shop vertices to find the one closest to
.
And this is done for all queries, separately.
Steps 2 and 3 should be straightforward to implement. There are many slightly different ways to do step 1. What makes it simpler is the fact that in a tree, there is exactly one path (that doesn't repeat vertices) between any two vertices. Thus the distance from
The idea is as follows. We know that the distance from
Algorithm 1: Breadth-first search to answer queries
queue ← a first-in, first-out queue, initially empty
dist ← an array, initially dist[v] = ∞ for any v
add R to the back of queue
dist[R] ← 0
while queue is not empty do
u ← first element of queue
remove the first element from queue
for each edge (u, v) from u do
if we have not visited v and (u, v) ≠ I then
add v to the back of queue
dist[v] ← dist[u] + weight(u, v)
end
end
end
Here,
A practical note: the answer to the question can be up to int
(in C++ and Java) will lead to overflow. Use long
(in Java) or long long
(in C++) instead.
Let's calculate the complexity of this solution. In algorithm 1, we visit each vertex and each edge at most once. Thus the complexity of step 1 is
If we only had one query, this would be optimal. But, we are neglecting the fact that all those queries take place on the same graph…
Subtask 3
(
From this point on, we consider our graph as a rooted tree, rooted at the exit vertex
Among the neighbours of a vertex, we distinguish between its parent and its children. For example, among the neighbours of
Why is this representation convenient? Suppose we erased an edge, for example, the edge escaped
for the vertices outside the subtree of escaped
if and only if
In this subtask, this is enough! If we can't escape the valley, the vertex escaped
. Now we only need a way to quickly tell if one vertex is in a subtree of another. There are various approaches, we describe a particularly elegant one.
Algorithm 2: A recursive function
Function dfs(u)
print u
for each child v of u do
call dfs(v)
end
print u
end
Consider the function
Let's look at the output of
After running
Subtask 4
(
We already know how to tell if the answer to a query is escaped
. Thus in this section, we only focus on calculating the answer in the other case. Suppose we have a query
Let
Let
where the minimum is taken over all shop vertices in the subtree of
Algorithm 3 provides a dynamic programming solution to calculate the array
Algorithm 3: Calculating magic.
Function buildMagic(u)
for each child v of u do
call buildMagic (v)
end
if u is a shop vertex then
magic[u] ← distE[u]
else
magic[u] ← ∞
end
for each child v of u do
magic[u] ← min(magic[u], magic[v])
end
end
call buildMagic (E)
for each vertex u do
magic[u] ← magic[u] - 2 distE[u]
end
Calculating
where the minimum is taken over the path from
Now we only need a way to calculate
For example, the 8th ancestor of a vertex is the 4th ancestor of its 4th ancestor. We can use these equations to initialize the arrays
How can we use these arrays to take minimum of
Algorithm 4: Binary lifting
Function buildLifting(u, p) /* p is the parent of u */
jumpVertex[u][0] ← p
jumpMagic[u][0] ← magic[u]
for k ← 1 to dlog2 Ne do
/* jumping dlog2 Ne is enough to get us to the root */
jumpVertex[u][k] ← jumpVertex[jumpVertex[u][k - 1]][k - 1]
jumpMagic[u][k] ← min(jumpMagic[u][k - 1], jumpMagic[jumpVertex[u][k - 1]][k - 1])
end
for each child v of u do
call buildLifting (v, u)
end
end
Function minPath(R, p)
/* calculates the minimum of magic over the path from R to p. */
u ← R
answer ← ∞
for k ← dlog2 Ne to 0 do
if jumpVertex[u][k] is in the subtree of p then
answer ← min(answer, jumpMagic[u][k])
u ← jumpVertex[u][k]
end
end
answer ← min(answer, magic[p])
return answer
end
To summarize:
- we call
and other auxiliary pre-processing functions; - we call
to initialize the binary lifting tables; - for each query
, where is the "lower vertex" of , answer the query as (orescaped
if is not in the subtree of ).
Step 1 should not take more than
Credits
- Task: Lukas Michel (Germany)
- Solutions and tests: Tähvend Uustalu, Andres Unt (Estonia)
Comments