Codeforces 1311E - Construct the Binary Tree

题目大意:

定义一颗二叉树,根节点编号为 1

令树上节点的深度为该节点到根节点的距离

给定两个数 n 和 d

问使用 n 个节点是否能够构成一颗总深度为 d 的二叉树

能,从第二个节点开始输出它的父节点

T<=1000  n,d<=5000  SUM(n)<=5000  SUM(d)<=5000

 

解题思路:

明显可以知道两个极端为完全二叉树与构成一条链的树

(图1)

 (图2)

因此给定 n ,可以求出满足题意的 d 的范围

首先可以得到 d 小于等于 n*(n-1)/2 ,这是构成一条链的树的节点最大深度和 (1+2+3+4+...+(n-1) )

最小深度为 2*1+4*2+8*3+16*4+... ( 2+4+8+16+...+2^x+y=n )

最小深度可以放在后面再判断,最大深度直接计算判断

如果 d 小于等于最大深度,那么可以通过最坏情况构造出上述 图2 的单链树

然后一个个地去移动深度最大的节点,将其往深度小的空位置移动

假设处理过程中的总深度为sumd,深度最小的且有空位的那一层层数为fulldepth,目前最大深度的层数为curdepth

移动操作可以分成两种情况:

  1、curdepth - fulldepth < sumd - d ,意思是就算把现在深度最大的节点移动到可以到达的深度最小的位置,对答案做出贡献后,总深度还是大于期望的深度 d,此时的操作就是直接移动到深度最低的地方

  2、curdepth - fulldepth >= sumd - d ,意思是目前这次移动如果直接移动到可以到达的深度最小的位置的话,对答案做出贡献后,总深度会等于期望深度 d 或者比 d 还要小,此时的操作应该是最大深度的节点往深度小的方向移动 sumd-d 层,移动完成后就是答案

上述移动将构成一个循环,循环条件是 sumd > d && curdepth > fulldepth,意思即目前总深度比期望深度大,且最大深度所在层数比最小深度空位所在层数要大,意思就是还能继续移动来更新对答案的贡献

直到循环条件不满足或者出现了第 2 种情况,循环退出

此时判断 sumd>d&&curdepth<=fulldepth 

如果上述表达式成立,说明此时已经构建成了一颗完全二叉树,但是总深度仍然大于期望深度,不满足题意,退出

否则,就说明答案的树已经构造完成,处理下答案输出即可

 

代码部分:

我使用了depcnt来储存各层还剩多少空位,光标直接采用上述的fulldepth

初始化 curdepth=n fulldepth=2 ,depcnt数组仅需开十几项即可,depcnt[ i ]=2 ^ ( i -1 )(2的 i-1 次方)

然后循环 i = 1~n建树,采用vector存树节点,数组名为v

如果 i 在depcnt支持的范围内,则让depcnt[ i ] -- ,说明这一层被占用了一个位置

每次往v[ i ]里push一个 i ,表明此时id为 i 的节点在第 i 层

然后进入循环

如果出现上述情况1,

  往v[ fulldepth ]内push此时第 curdepth 层的第一个元素(也只有那一个),表示把最大深度节点移动到了最小深度的空位

  然后sumd - = curdepth - fulldepth ,表示对答案做出的贡献

  curdepth -- ,表示最大深度减小

  depcnt [ fulldepth ] -- ,表示最小深度层空位-1

  如果depcnt[fulldepth] == 0 ,说明最小深度层没有空位了,则让fulldepth ++

如果出现上述情况2,

  往v[ curdepth-(sumd-d) ]内push此时第 curdepth 层的第一个元素,表示把最大深度节点移动到可以直接满足答案的层数

  sumd=d,以跳过接下来的判断

  curdepth-- ,最大深度减小

  break

判断结束后,此时vector中的节点基本无序

所以开了一个ans数组,存某个节点的父节点 id

从第 2 层开始循环到第 curdepth 层

因为总满足 第 i 层节点个数 <= 2* 第 i-1 层节点个数

所以每一层第 j 个数的父节点可以直接连接到上一层的第 j/2 个节点(因为vector下标从0开始,用了整除的向下取整)

最后,从 2 到 n 输出 ans 数组即可

 


(另,本题还能用深搜写,但是没试过)

(31ms/2000ms)

#include<bits/stdc++.h>
using namespace std;
int depcnt[30],ans[5050];
void solve(){
    int n,d,i,j;
    cin>>n>>d;
    int sumd=n*(n-1)/2;
    if(sumd<d){
        cout<<"NO\n";
        return;
    }//大于最大深度,不满足题意
    vector<int> v[5050];
    depcnt[1]=1;
    for(i=2;i<=25;i++)
        depcnt[i]=depcnt[i-1]*2;//第i项最大个数为2的i-1次方
    for(i=1;i<=n;i++){
        if(i<=25)
            depcnt[i]--;
        v[i].push_back(i);
    }
    int curdepth=n,fulldepth=2;
    while(sumd>d&&curdepth>fulldepth){
        if(curdepth-fulldepth<sumd-d){
            v[fulldepth].push_back(v[curdepth][0]);
            sumd-=curdepth-fulldepth;
            curdepth--;
            depcnt[fulldepth]--;
            while(!depcnt[fulldepth])
                fulldepth++;
        }
        else{
            v[curdepth-(sumd-d)].push_back(v[curdepth][0]);
            sumd=d;
            curdepth--;
            break;
        }
    }
    if(sumd>d&&curdepth<=fulldepth){
        cout<<"NO\n";
        return;
    }//小于最小深度,不满足题意
    cout<<"YES\n";
    for(i=2;i<=curdepth;i++){
        int cnt=v[i].size();
        for(j=0;j<cnt;j++)
            ans[v[i][j]]=v[i-1][j/2];
    }
    for(i=2;i<=n;i++)
        cout<<ans[i]<<' ';
    cout<<'\n';
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int T;cin>>T;while(T--)
        solve();
    return 0;
}

 

posted @ 2020-03-11 17:19  StelaYuri  阅读(179)  评论(0编辑  收藏  举报