Prepared by: Ante Ðerek, Luka Kalinovčić, Paula Vidas, and Dominik Fistrić
Fun fact: This task was originally prepared for CEOI 2013 (also in Croatia). However, it was deemed too difficult, especially because the contestants didn't have full feedback back then, and thus it didn't appear on the contest. Task idea was proposed by Ante Ðerek and solution was found by Luka Kalinovčić. To honor them, this year's statement featured Ante and Luka as the main characters.
Note that in any tree with maximum degree at most three, we can always find a node with degree at most two. If we root the tree in such a node, every node will have at most two children (i.e. it's a binary tree).
Let's start with the first subtask. Let
be the given points, such that they form vertices of a convex polygon in that order. Place the root of the binary tree in any of the points, say
. Let
be the size of the subtree of one of its children. We can place this child in node
and decide to place its whole subtree in points
in some way. If the node has two children, we place the second child in node
and its subtree in
. We then solve the subproblems recursively. A careful implementation of this strategy has complexity
.
We will now describe a solution for subtasks 2 and 3. Similarly to subtask 1, place the root in some point
on the convex hull of the given point set. Sort all other points by the polar angle around point
. In other words, we will sort them such that if point
is before point
, then points
are ordered counter-clockwise. Again, if
is the subtree size of a child, place this child's subtree in the first
points, choosing the first of the points as the one where the child goes. If there are two children, the other child's subtree is placed in the remaining points, and the child is placed in the first of them.
Time complexity of this approach is
if we sort the points each time, which is enough for the second subtask. It can be sped up to
if we use some selection algorithm with expected complexity
(e.g. std::nth_element
in C++) to find the
smallest element. This is fast enough to pass the third subtask.
Finally, we describe the full solution. Notice that for now, we used a procedure that takes a set of points with one distinguished point on the hull, and a tree with one distinguished node, and draws the tree on those points, such that the distinguished node is placed in the distinguished point. It turns out that it's possible to design a procedure that does the same but given two pairs of distinguished points and nodes, such that the points lie on the hull and are adjacent.
The high-level idea is the following: If we're given one point-node pair
, such that
is on the hull and the tree is rooted in
, then we choose some leaf
and one of the adjacent points on the hull as
, and
is our second pair, so we're in the two pair case. Assume now we're given two point-node pairs
and
, such that
is the root of the tree, while
is a leaf, and
and
are two adjacent points on the hull. Take
to be some node on the path between
and
, and take
to be a point such that the gray triangle
doesn't contain any other points.

Then, we can construct three subproblems, blue, green, and yellow, shown in the figure below. The blue, green, and yellow regions are chosen such that the number of points in each region matches the size of the respective tree, and the points in a region form a consecutive subsequence if we sort all points by polar angle around
. We distinguish two cases, when
is not on the hull and when it is.

It can be proven that there always exists a "good" point
, i.e. a point such that the gray region has no other points inside,
is a line segment on the hull of the blue region,
is on the hull of the green region, and
is a line segment on the hull of the yellow region. This is left as an exercise for the reader.
The three subproblems can be solved recursively. In the blue problem we're given point-node pair
as root and
as leaf, in the yellow problem we're given point-node pair
as root and
as leaf, and in the green problem we're given a point-node pair
as root.
Now it's time to analyse the time complexity.
When choosing nodes
or
, it's not optimal to simply take any node. When we're given root
and need to choose a leaf
, we'll do it this way: start from the root, and at each step move down to the "heavy" child, i.e. the one with the larger subtree size, until a leaf is reached. When we're given root
and leaf
, for node
we'll take the midpoint of the path between
and
.
Consider a two point-node pair subproblem with
nodes. Let
denote the length of the path from
to
, and let's look at the subtrees attached to this path that belong to the "light" children. Denote the sizes of these subtrees as
. Notice that
, and
for all
, because they are the light children. Note that each subproblem can be solved in time that is linear in the size of the subproblem (not counting the recursive calls). Therefore, after
steps of the process described above, we'll reach all the subproblems of size
, and will have spent
time up that point. Consequently, if
denotes the time required to solve a subproblem of size
, we obtain:

This recurrence relation works out to be
.
If in each subproblem we sort the points by angle, a factor of
is added to the complexity, and that solution is fast enough for subtask 4. However, sorting is not necessary if we use std::nth_element
, and that solution should achieve full score for this task.
Comments