先去考虑找一下无解条件。
首先就是有 \(d\) 关于 \(n\) 的下界 \(L\),就是弄成一颗完全二叉树的答案。
其次有 \(d\) 关于 \(n\) 的上界 \(R\),就是成一条链的样子。
首先当 \(d < L\) 或 \(R < d\) 时显然无解。
对于 \(L\le d\le R\) 又如何去判定。
能发现没有一个比较好的判定方法,于是直接考虑猜想任意 \(L\le d\le R\) 都存在解。
那么就可以考虑去通过构造来证明有解了。
因为要满足 \(L\le d\le R\) 都有解,一种想法就是先得到 \(d = L\) 的特殊解,然后去考虑调整法去调整至每次 \(+1\)。
相当于就是先弄出来完全二叉树,接下来考虑每次选一个距离为 \(x\) 的点,更换其父亲使得其距离变为 \(x + 1\)。
手玩一下能发现随便搞一搞都能有解,这里给出一种方式:
考虑找到一个有两个儿子的点,且满足两个儿子往下都是一条链。
例如有链 \(l_1, l_2\) 分别为 \(l_1 = (x, a_1, a_2), l_2 = (x, b_1, b_2, b_3)\)。
写下来考虑找到短的那一条链的尾端,\(a_2\)。
然后把 \(a_2\) 接到 \(b_2\) 后面,此时距离就从 \(2\) 变为了 \(3\),且因为 \(l_2\) 是链肯定能接。
接下来又去对于 \(l_1' = (b_2, a_2), l_2' = (b_2, b_3)\) 来做,这两条链也符合上面的条件。
能发现只有成为一条链时无法继续操作,而此时正好顶到了上界 \(R\)。
真正构造的时候并不需要模拟这个过程,因为上面只是说肯定存在解。
实际上构造时只需要维护二叉树每一层的点的个数 \(cnt_i\),能往下一层 \(+1\) 就 \(+1\)(需要满足 \(2cnt_i\ge cnt_{i + 1}\)),最后加到 \(d\) 就终止。
知道了每一层的点的个数,就不难构造这个二叉树了。
时间复杂度 \(\mathcal{O}(dn)\)。
#include<bits/stdc++.h>
inline void Main() {
int n, d; scanf("%d%d", &n, &d);
int lb = 0;
for (int i = 0, n_ = n; n_; i++)
lb += i * std::min(1 << i, n_), n_ -= std::min(1 << i, n_);
int rb = n * (n - 1) / 2;
if (d < lb || rb < d)
return puts("NO"), void();
std::vector<int> cnt(n);
for (int i = 0, n_ = n; n_; i++)
cnt[i] = std::min(1 << i, n_), n_ -= std::min(1 << i, n_);
d -= lb;
while (d) {
bool f = 0;
for (int i = n - 2; ~ i; i--)
if ((cnt[i] - 1) * 2 >= (cnt[i + 1] + 1)) {
cnt[i]--, cnt[i + 1]++, f = 1;
break;
}
assert(f);
d--;
}
puts("YES");
std::vector<std::vector<int> > id(n, std::vector<int>());
for (int i = 0, n_ = 0; i < n; i++)
for (int j = 1; j <= cnt[i]; j++)
id[i].push_back(++n_);
for (int i = 1; i < n; i++)
for (int j = 1; j <= cnt[i]; j += 2) {
printf("%d ", id[i - 1][j >> 1]);
if (j + 1 <= cnt[i]) printf("%d ", id[i - 1][j >> 1]);
}
puts("");
}
int main() {
int T; scanf("%d", &T);
while (T--) Main();
return 0;
}
标签:std,Binary,cnt,le,1311E,int,Tree,++,二叉树
From: https://www.cnblogs.com/rizynvu/p/18301590