Codeforces 1311E Construct the Binary Tree
Description
要求构造一个 $n$ 个节点的二叉树(每个节点拥有不超过 $2$ 个孩子),节点 $1$ 为根,要使所有节点到根的距离之和为 $d$ 。要求先判断可不可以构造,如果可以输出 YES,下一行输出 $2$ 到 $n$ 号节点的父亲节点,否则输出 NO。有多组询问。
Solution
首先,我们可以先确定可行的 $d$ 的范围。
显然,当二叉树是一条链的时候,$d$ 是最大的,也就是说,$0+1+2+\cdots (n-1) = d_{\max}$,稍微转化一下,就是 $d \le \frac{n(n-1)}{2}$。
而把这颗二叉树给尽量满的构造 $d$ 显然是最小的,也就是说,确保这棵二叉树是完全二叉树,就可以得到 $d_{\min}$,这个不妨递推一下,如果用 $dep_i$ 表示第 $i$ 个结点的深度,那么显然有 $dep_{2i} = dep_i + 1, dep_{2i+1} = dep_i + 1$(类比线段树很容易理解),当然,这个和 $dep_i = dep_{\lfloor\frac i 2 \rfloor}+1$ 是等价的。
那么在 $[d_{\min}, d_{\max}]$ 之间的 $d$ 都是可行的了,观察到 $n, d$ 都很小,可以尝试一步步的转移。先构造出一棵完全二叉树,每次 找到一个可行的深度最大的点,把它向下移动一层。
为了实现方便,不妨把 $dep = i$ 的点存到一个 vector 中,名字叫做 $\text{node}_i$,现在试图把某一个 $dep = i$ 的点下移,要求显然就是 去掉这个点后,下面一层还有位置,可以表现为 $\operatorname{size}(\text{node}_{i+1})<(\operatorname{size}(\text{node}_i) - 1) \times 2$,暴力下移即可。
最后我们得到的是若干个 vector 数组,令 $\text{node}_{i,j}$ 的儿子是 $\text{node}_{i+1,2i}$ 和 $\text{node}_{i+1,2i+1}$ 就行了(当然,前提是存在)。
#include <bits/stdc++.h> using namespace std; const int N = 5005; int n, dep[N], d, mxdep, sumdep, fa[N]; vector<int> node[N]; int main() { int t; cin >> t; while(t--) { cin >> n >> d; for(int i = 1; i <= n; i++) node[i].clear(); dep[1] = mxdep = sumdep = 0; node[0].push_back(1); for(int i = 2; i <= n; i++) { dep[i] = dep[i >> 1] + 1; sumdep += dep[i]; mxdep = max(mxdep, dep[i]); node[dep[i]].push_back(i); } if(d < sumdep || d > n * (n - 1) / 2) { cout << "NO\n"; continue; } while(sumdep < d) { for(int i = mxdep; ~i; i--) { if(node[i].size() >= 2 && node[i + 1].size() < node[i].size() * 2 - 2) { sumdep++; mxdep = max(mxdep, i + 1); node[i + 1].push_back(node[i].back()); node[i].pop_back(); break; } } } for(int i = 0; i <= mxdep; i++) { for(int j = 0; j < node[i].size(); j++) { int ls = j * 2, rs = j * 2 + 1; if(ls < node[i + 1].size()) fa[node[i + 1][ls]] = node[i][j]; if(rs < node[i + 1].size()) fa[node[i + 1][rs]] = node[i][j]; } } cout << "YES" << endl; for(int i = 2; i <= n; i++) cout << fa[i] << ' '; cout << endl; } }