A prefix-free code over the three characters 0,
1, * is exactly a rooted tree in which every
node has up to three children, reached by an edge labelled
0 (cost 1), 1 (cost 1), or *
(cost \(h\)). The codewords are the
root-to-leaf label strings, one leaf per symbol, and the cost of a leaf
is the total edge cost on its path from the root – its depth,
measured in nanoseconds. If symbol \(i\) occurs \(a_i\) times, the message takes \(\sum_i a_i\cdot(\text{depth of }i)\)
nanoseconds to transmit, so we want a tree with \(s\) leaves that minimizes the weighted sum
of leaf depths.
Two facts hold regardless of \(h\):
Sorted assignment. Only the multiset of leaf depths matters. Given the depths, an optimal assignment puts the heaviest symbol on the shallowest leaf. This can be proven by a simple exchange argument: if a heavier symbol sat deeper than a lighter one, swapping them would improve the total transmission cost. Hence, if we knew the shape of the tree, we could just sort the weights in decreasing order and fill them into leaves from top to bottom.
No single-child internal nodes. In an optimal tree every internal node has at least two children: a node with a single child can be contracted – i.e., we can remove it and instead attach its children directly to its parent. This would strictly decrease the depth of every leaf below it. Hence an optimal tree has at most \(s-1\) internal nodes.
The corner case \(s=1\) is handled
separately: the one codeword must be non-empty, and assigning the symbol
0 as the codeword is clearly always optimal. This was
helpfully covered by an example. Below we’ll assume that \(s\geq 2\).
When \(h=1\), all three characters cost 1, so we are just looking for an optimal Huffman code with a three-letter alphabet.
A ternary merge tree reduces the number of active items by 2 with each merge and must end at a single root, so the number of leaves has to be odd. If \(s\) is even we therefore add one dummy symbol of weight \(0\) so that we don’t have to handle special cases.
It can now be shown that there is always an optimal tree in which each inner node is full, i.e., has all three children. (The proof is again via switching arguments: any other tree can be rearranged into this shape without increasing the total cost.)
The resulting algorithm is then greedy: repeatedly remove the three smallest weights from a min-heap, merge them into one node whose weight is their sum, add that sum to the answer, and push the new element back onto the heap. Stop when just one node remains. Each symbol’s weight is added once per level above it, so the total of all merge sums equals the weighted depth sum.
Correctness follows from the standard Huffman argument adapted to degree 3. Once we know that, we know that the three lightest elements will all end up in the bottommost layer of the tree, and without loss of generality we may assume that they are siblings. We can now merge them together and create one new symbol whose weight is the total weight of the three original symbols. All that remains is constructing the optimal tree for this new set of \(n-2\) symbols, and that can be done inductively – by repeating the same argument.
As this construction can explicitly construct the tree, we can then just read the codewords off the paths from its root to its leaves.
This runs in \(O(s\log s)\) per test and covers subproblem B3.
When * costs 2, the three edges no longer have equal
cost, and no Huffman-style greedy is known. Instead we are going to
explicitly optimize over the shape of the tree, described level
by level.
For a fixed tree and each level \(\ell\ge
0\) (depth in nanoseconds) let \(n_\ell\) be the number of leaves at depth
\(\ell\) and \(m_\ell\) the number of internal nodes at
depth \(\ell\). Every node at level
\(\ell\) is either a
0/1-child of an internal node at level \(\ell-1\) or the *-child of an
internal node at level \(\ell-2\),
so
\[ n_\ell + m_\ell \;\le\; 2\,m_{\ell-1} + m_{\ell-2}, \]
with \(m_0=1\) (the root is internal, as \(s\ge 2\)) and \(m_{-1}=0\). Conversely, any sequence satisfying these inequalities with \(\sum_\ell n_\ell = s\) comes from some tree: process levels top-down, and at each level there are exactly \(2m_{\ell-1}+m_{\ell-2}\) child slots available, which by the inequality is enough to host the required \(n_\ell+m_\ell\) nodes. Internal nodes that end up childless are pruned without changing any leaf depth. So minimizing over trees is exactly minimizing over such profiles.
The first fact from the introduction allows us to calculate the cost of any such profile: Sort the weights decreasingly, let \(A_p\) be the sum of the \(p\) largest and \(W=A_s\) the total. Charge the cost one level at a time: advancing from level \(\ell\) to \(\ell+1\) costs \(W - A_p\), where \(p\) is the number of symbols already placed at levels \(\le\ell\), because each not-yet-placed symbol gains one more level of depth. Summing these charges reproduces \(\sum_i a_i\cdot(\text{depth})\) exactly.
This suggests a dynamic programming algorithm whose state after deciding level \(\ell\) is
\[ (p,\ u,\ v) \;=\; (\text{symbols placed},\ m_{\ell-1},\ m_\ell). \]
The level index \(\ell\) itself is not part of the state – the transition cost \(W-A_p\) does not depend on it. We start in \((0,0,1)\) with cost \(0\). From \((p,u,v)\) the next level has capacity \(c = 2v+u\) slots; a transition costs \(W-A_p\) and chooses how many \(n\in\{0,\dots,c\}\) of them become leaves, moving to \((p+n,\ v,\ c-n)\). Two observations keep this small and correct:
The transition graph is acyclic – \(n\ge 1\) increases \(p\), and the \(n=0\) step maps \((u,v)\mapsto(v,2v+u)\), which strictly increases the capacity – so a plain forward-direction DP in order of increasing \(p\) (and, within equal \(p\), increasing capacity) suffices. The answer is the smallest cost over all finishing transitions. This is \(O(s^4)\) per test with a tiny constant, comfortably covering the \(h=2\) test cases in all remaining subproblems.
To reconstruct a code, store each state’s predecessor and chosen
\(n\), walk back to recover the
per-level leaf counts \(n_1,\dots,n_L\), then build the codewords
top-down: maintain the list of slot strings arriving at the current
level (and, separately, those arriving one level later via
*-edges); at each level the first \(n_\ell\) slots become leaves and receive
the next symbols in decreasing weight order, while each remaining slot
\(x\) stays internal and emits
x0, x1 to the next level and x*
two levels down. Slots on the same level are interchangeable, so any
assignment of the required counts works – we can do it simply left to
right.