详细讲解Codeforces Round #624 (Div. 3) E. Construct the Binary Tree(构造二叉树)

题意:给定节点数n和所有节点的深度总和d,问能否构造出这样的二叉树。能,则输出“YES”,并且输出n-1个节点的父节点(节点1为根节点)。

题解:n个节点构成的二叉树中,完全(满)二叉树的深度总和最小,单链树(左/右偏数)的深度总和最大。若d在这个范围内,则一定能构造出来;否则一定构造不出来。

    1.初始构造一颗单链树,依次把底部的节点放入上面的层,直到满足深度总和为d

    2.若当前深度总和sum > d,则先拿掉底端节点。

      拿掉后,若sum依然比d大,就直接把底端节点放入有空位的最上层;

      拿掉后sum <= d,dif = d - sum。

        若dif >= 此时有空位的最上层深度,则深度为dif的层一定有空位,把底端节点放入该层,即可完成构造。

        否则,依然把底端节点放入有空位的最上层,修改后的sum依旧比d大,继续循环即可。

    3.退出循环后就完成了构造,获得了所求的树。

具体存储结构、表示方式和算法过程见代码(和注释):

 1 #include<cstdio>
 2 #include<cstring>
 3 using namespace std;
 4 /*
 5 9 21
 6 YES
 7 1 1 2 2 4 4 6 8
 8 9 22
 9 YES
10 1 1 2 2 4 6 6 7
11 */
12 int layer[5005], num[5005];    //layer[i]先存第i+1个点所在层的深度,num[i]是深度为i的层里的节点数
13 
14 int main() {
15     int t, n, d;
16     scanf("%d", &t);
17     while (t--) {
18         scanf("%d%d", &n, &d);
19         memset(num, 0, sizeof num);
20         int sum = n * (n - 1) / 2, dep = 0, minn = 0;
21         num[0] = 1;        //0深度层只有一个根节点
22         for (int i = 2; i <= n; i++) {
23             //i&(i - 1)的结果为把i二进制下最后一个1置0。i&(i - 1) == 0时,i为2的整数次幂
24             if ((i&(i - 1)) == 0)dep++;        //第i+1层的第一个节点为2^i
25             minn += dep;                    //minn记录满二叉树时的深度总和
26 
27             layer[i - 1] = i - 1;    //单链树时,和为sum,layer[i]是第i+1个点所在层的深度
28             num[i - 1] = 1;            //num[i]记录深度为i的层的节点总数
29         }
30         if (d<minn || d>sum) {
31             puts("NO");
32             continue;
33         }
34         puts("YES");
35         dep = 1;    //当前有空位的最上层的深度
36         for (int i = n - 1; i > 0 && sum > d; i--) {
37             sum -= i;        //拿掉底端顶点
38             num[i]--;
39             if (sum > d) {            //拿掉之后,sum仍然比d大时;直接放最上面
40                 layer[i] = dep;        //第i+1个点现在的深度为dep
41                 sum += dep;
42 
43                 if (++num[dep] == (1 << dep))dep++;    //若最上面的层满了,修改为下一层
44             }
45             else {                    //拿掉之后,sum<=d时
46                 int dif = d - sum;        //看差值对应的层是否有空位
47                 if (dif >= dep) {            //有空位,则直接放到深度等于差值的那一层,构造成功
48                     layer[i] = dif;
49                     sum += dif;    //写出来更好理解
50                     num[dif]++;    //该层节点数++
51                     break;
52                 }
53                 else {                        //无空位,只能放最上面dep层
54                     layer[i] = dep;
55                     sum += dep;                //此时sum仍然 > d
56                     if (++num[dep] == (1 << dep))dep++;    //若最上面的层满了,修改为下一层
57                 }
58             }
59         }
60         //构造成功。layer[i]是原来单链树中深度为i的点(第i+1个点) 现在的深度,num[i]是第i层的节点总数
61         //现只用num中的信息求解;layer中的信息只是辅助理解,现在用来存最终答案(即第i个节点的父节点编号)
62         int id(2), fid(1);    //当前节点编号,上一层首个节点的编号
63         for (int i = 1; num[i]; i++) {    // while(深度为i的层节点数不为0)
64             for (int j = 0; j < num[i]; j++) {
65                 //深度为i的层的第j个节点,在完全二叉树中的编号为(1<<i)+j,上一层首个节点编号为1<<(i - 1)
66                 //layer[id++] = fid + ((1 << i) + j) / 2 - (1 << (i - 1)); 直接算这个式子会溢出
67                 layer[id++] = fid + j / 2;    //简化后得出,也可以直接理解推出来
68             }
69             fid += num[i - 1];
70         }
71         for (int i = 2; i < n; i++)
72             printf("%d ", layer[i]);
73         printf("%d\n", layer[n]);
74     }
75     return 0;
76 }

完全二叉树编号:

          1

    2            3

 4     5      6      7

8  9  10  11  12  13  14  15 

 

posted @ 2020-02-27 16:32  随~心  阅读(364)  评论(0编辑  收藏  举报