题解
挺水的一道题. Rating $ color{orange} {2300}$ 以下送命题。
首先我们知道,所有子树大小之和就是节点个数加上从根到所有节点的路径长度之和。
他要求度数尽可能小,所以我们二分度数(k).显然,度数越小,子树和越大。
对于一个(k)叉树,他的子树大小之和为(n+k^2+k^3+...+rem)
我们通过二分得到最大的边界(k)
然后,此时我们的子树大小(s)是要小于等于规定的子树大小和的。
我们考虑扩大子树大小。
显然,我们让某些节点,往深度扩展将会扩大我们的子树大小。
我们记录每个深度的节点个数,已经每个节点的深度。
我们尝试从深度最深的节点开始往下扩展,直至子树大小达到规定大小。
Tips:n-1叉树的大小显然为2n-1 而一条链的大小为 n(n+1)/2 。如果k不在这个范围内,则无解。
具体实现非常简单。代码如下
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int maxn = 1e6+10;
ll n,k,cnt[maxn],d[maxn];
bool check(int x) {
ll i(2),j,t=1,num=k-n,dep=0;
memset(d,0,sizeof d);
memset(cnt,0,sizeof cnt);
while(i<=n) {
t*=x;++dep;
for(j=1;j<=t&&i<=n;++i,++j) cnt[dep]++,d[i]=dep,num-=dep;
}
if(num<0) return false;
j=n;
while(num) {
++dep;
if(cnt[d[j]]==1) --j;
t = min(num,dep-d[j]);
cnt[d[j]]--;
d[j]+=t;
cnt[d[j]]++;
num-=t;
--j;
}
return true;
}
int main() {
cin>>n>>k;
if(k<(1LL*2*n-1)||k>(1LL*n*(n+1)/2)) {
puts("No");
exit(0);
}
int l = 1, r = n;
while(l<r) {
int mid = (l+r)>>1;
if(check(mid)) r=mid;
else l = mid+1;
}
check(r);
puts("Yes");
int pos;
d[pos=1]=0; sort(d+2,d+1+n); memset(cnt,0,sizeof cnt);
for(int i = 2;i<=n;++i) {
while(d[pos]!=d[i]-1||cnt[pos]==r) ++pos;
cout<<pos<<' ';cnt[pos]++;
}
return 0;
}