Round 1111 (Part 2) E. Building a tree — Another idea?

Revision en1, by TuanKiet2004, 2026-07-27 13:15:22

Hello everyone!

In this blog, I will describe an (O(n)) construction for E. Building a Tree.

The main ideas are:

  • checking the valid range of (k);
  • rooting the tree at vertex (1);
  • separating even and odd vertices into two branches;
  • reducing the required distance sum to a depth sum;
  • greedily assigning depths.

    1. Problem Restatement

We need to construct a tree with vertices (1,2,\ldots,n) such that

$$$ \sum_{i=1}^{n} \operatorname{dist}\left(i,(i\bmod n)+1\right)=k. $$$

Equivalently,

$$$ \operatorname{dist}(1,2) +\operatorname{dist}(2,3) +\cdots +\operatorname{dist}(n-1,n) +\operatorname{dist}(n,1) =k. $$$

If no such tree exists, we output (-1).

2. Necessary Conditions

For any edge of the tree, removing it splits the tree into two connected components.

While traversing the cyclic order

$$$ 1\rightarrow2\rightarrow\cdots\rightarrow n\rightarrow1, $$$

every time we cross from one component to the other, we must later cross back.

Therefore, every edge contributes an even number of times to the total sum, so

$$$ k $$$

must be even.

Also, every edge of the tree is used at least twice. Since a tree has (n-1) edges,

$$$ k\ge 2(n-1). $$$

The maximum possible value is

$$$ \left\lfloor\frac{n^2}{2}\right\rfloor. $$$

Thus, a solution exists only if

$$$ k\equiv0\pmod2 $$$

and

$$$ 2(n-1)\le k\le \left\lfloor\frac{n^2}{2}\right\rfloor. $$$

In the code:

ll mn = 2LL * (n - 1);
ll mx = 1LL * n * n / 2;

if (k % 2 || k < mn || k > mx) {
    cout << -1 << '\n';
    return;
}

3. Two-Branch Construction

Root the tree at vertex (1).

All even vertices belong to one branch, and all odd vertices greater than (1) belong to another branch.

For example:

        1
      /   \
     2     3
     |     |
     4     5
     |     |
     6     7

The branches do not need to be simple paths. A vertex may be connected to any earlier vertex of the same parity.

The important property is that every consecutive pair (i,i+1) has opposite parity.

Hence, for every

$$$ 2\le i \lt n, $$$

the path from (i) to (i+1) passes through vertex (1), so

$$$ \operatorname{dist}(i,i+1) = \operatorname{depth}(i) + \operatorname{depth}(i+1). $$$

The pairs ((1,2)) and ((n,1)) contribute

$$$ \operatorname{depth}(2) $$$

and

$$$ \operatorname{depth}(n) $$$

respectively.

Therefore, every non-root vertex contributes its depth exactly twice:

$$$ \sum_{i=1}^{n} \operatorname{dist}\left(i,(i\bmod n)+1\right) = 2\sum_{v=2}^{n}\operatorname{depth}(v). $$$

So we only need

$$$ \sum_{v=2}^{n}\operatorname{depth}(v) = \frac{k}{2}. $$$

In the code:

ll rem = k / 2;

Here, rem is the remaining depth sum that still needs to be assigned.

4. Stored Information

For each parity, we maintain:

vector<vector<int>> rep(2, vector<int>(n + 1));
int dep[2] = {0, 0};

The meaning is:

  • dep[p] is the maximum depth currently used by parity p;
  • rep[p][d] stores one vertex of parity p at depth d.

For a vertex (v),

int p = v & 1;

determines its parity.

If we want to place (v) at depth (d>1), we need an existing vertex of the same parity at depth (d-1).

Therefore,

$$$ d\le dep[p]+1. $$$

This is represented by

int lim2 = dep[p] + 1;

5. Greedy Choice

Suppose we are processing vertex (v).

After processing it, there are

int lef = n - v;

vertices remaining.

Every remaining vertex must have depth at least (1). Therefore, we must reserve at least lef units of the remaining sum.

Thus, the current depth cannot exceed

ll lim1 = rem - lef;

The tree structure gives another upper bound:

int lim2 = dep[p] + 1;

Therefore, we choose

int d = min<ll>(lim1, lim2);

This assigns the largest possible valid depth to the current vertex while leaving enough depth for the remaining vertices.

If

d < 1

then the construction is impossible:

if (d < 1) {
    cout << -1 << '\n';
    return;
}

6. Choosing the Parent

If the chosen depth is (1), the vertex is connected directly to the root:

if (d == 1)
    u = 1;

Otherwise, we connect it to a vertex of the same parity at depth (d-1):

else {
    u = rep[p][d - 1];
    if (!u) {
        cout << -1 << '\n';
        return;
    }
}

Then we store the edge:

ans.push_back({u, v});

and update the data structures:

rep[p][d] = v;
dep[p] = max(dep[p], d);

Finally, subtract the assigned depth from the remaining sum:

rem -= d;

7. Why the Greedy Works

The greedy uses two limits.

First,

d <= dep[p] + 1

guarantees that the chosen depth is constructible.

If (d=1), we connect to vertex (1).

If (d>1), a vertex at depth (d-1) already exists in the same parity branch and is stored in

rep[p][d - 1].

Second,

d <= rem - lef

guarantees that at least one unit remains for every unprocessed vertex.

So the algorithm never spends too much of the remaining depth sum.

After processing all vertices, the construction is valid only if

rem == 0

which is checked by

if (rem) {
    cout << -1 << '\n';
    return;
}

Then

$$$ \sum_{v=2}^{n}\operatorname{depth}(v) = \frac{k}{2}, $$$

and therefore

$$$ \sum_{i=1}^{n} \operatorname{dist}\left(i,(i\bmod n)+1\right) = 2\cdot\frac{k}{2} = k. $$$

8. Complexity

Each vertex is processed exactly once.

The time complexity is

$$$ \boxed{O(n)}. $$$

The memory complexity is

$$$ \boxed{O(n)}. $$$

9. Implementation

https://codeforces.me/contest/2247/submission/384493178

Thanks for reading!

History

 
 
 
 
Revisions
 
 
  Rev. Lang. By When Δ Comment
en1 English TuanKiet2004 2026-07-27 13:15:22 6117 Initial revision (published)