要求构造一个 个节点的二叉树,节点 为根,要使所有节点到根的距离之和为 。要求先判断可不可以构造,如果可以输出
YES
,下一行输出 到 号节点的父亲节点,否则输出NO
。
首先显然可以增量构造,每次把某个叶子接到同层的节点下,令深度总和增加 。
那么设深度总和下界(构造一棵完全二叉树)为 ,深度总和上界(构造一条链)为 ,显然 或者 的情况下都是无解的。不妨猜测反之一定有解,尝试证明之,那么由于可以增量构造,所以只需要证明从完全二叉树开始,每次一定能把某个叶子接到同层的节点下。
显然每次操作深度尽量深的叶子是最优的,因为深度深的叶子被移走之后深度较浅的节点更容易暴露出来,成为新的可操作的叶子,并且接深度尽量深的叶子不会减少深度浅的叶子的数量。
那么由于每次都操作深度尽量深的叶子,所以一旦 被操作了,并且下一次还能操作它,那么下一次操作一定还是操作它,所以 的轨迹一定是这样的:

那么最后肯定能形成一条链,证毕。
代码如下:
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
const int S=5005;
int n,k;
bool vis[S];
int fat[S],du[S];
int dep[S];
int idx[S];
inline void slove()
{
scanf("%d%d",&n,&k);
int R=n*(n-1)/2;
for(int i=1;i<=n;i++) vis[i]=false,du[i]=0;
queue<int> q;
q.push(1);
vis[1]=true;
dep[1]=0;
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=1,cnt=0;i<=n&&cnt<2;i++)
{
if(!vis[i])
{
vis[i]=true;
fat[i]=u;
du[u]++;
dep[i]=dep[u]+1;
cnt++;
q.push(i);
}
}
}
int L=0;
for(int i=1;i<=n;i++) L+=dep[i];
if(k<L||k>R) return void(puts("NO"));
puts("YES");
for(int tme=1;tme<=k-L;tme++)
{
for(int i=1;i<=n;i++) idx[i]=0;
for(int i=1;i<=n;i++) if(du[i]<2&&(idx[dep[i]]==0||du[i]>0)) idx[dep[i]]=i;
int u=0;
for(int i=1;i<=n;i++) if(du[i]==0&&idx[dep[i]]!=0&&i!=idx[dep[i]]&&(u==0||dep[i]>dep[u])) u=i;
du[fat[u]]--;
du[fat[u]=idx[dep[u]]]++;
dep[u]++;
}
for(int i=2;i<=n;i++) printf("%d ",fat[i]);
printf("\n");
}
int main()
{
int T;
scanf("%d",&T);
while(T--) slove();
return 0;
}