1 条题解
-
0
好的,我们先来分析题意和标程的思路,然后给出详细的题解。
题目重述
有一棵有 个节点的有根树,节点编号 ,根为 。
定义节点 的“神圣度” 为从根到 的路径上最小的节点编号。
总神圣度定义为:给定 ,要求构造这样一棵树,输出根和 条边,若无解输出 。
关键观察
-
根的神圣度
根 的路径只有自己,所以 。 -
最小值的传递性
设 为 的父节点,则也就是说,从根到 的路径上,一旦遇到一个比当前最小值更小的节点,后面的节点神圣度都会变成这个更小的值。
-
结构等价
如果根是 ,那么整个树的节点都会“继承” 作为最小值的一部分,直到碰到一个比 更小的节点。
但根是 ,路径上的最小值不可能小于 ,除非某个后代节点编号更小?
不对,编号是固定的,编号 1 的节点一定在任何包含它的路径上成为最小值,因此如果 1 在某个子树里,那该子树的所有节点神圣度都是 1。
更简单的视角
设根为 ,那么:
- 节点 :
- 节点 会怎样?
注意:如果某个节点 ,它一定不在根到其他节点的路径上作为祖先(否则它会更早成为最小)。
实际上,我们可以这样构造:
根为 ,然后我们尝试所有节点 放在哪里。
观察一个简单构造:
- 把 1 直接挂在 下面,那么 (因为路径 最小值 1)。
- 2 如果挂在 1 下面,则 ,这样 2 贡献 1 而不是 2。
所以为了使得 ,节点 必须放在一条路径上,且这条路径上的所有祖先都 。
转化问题
如果我们把 看作节点 的“分值”,那么根的分值 = 自身编号,其他节点如果挂在编号更小的节点下,分值会被拉低。
更精确地说:
- 若节点 是 的祖先且 ,则 。
- 为了最大化总和,我们希望每个节点 的分值尽量大,即让它放在一条编号递增的路径上(祖先都 > 它,但实际上 比祖先小也不行,所以不可能有祖先都 > 它,除了根更大,根自己是特殊的:根的分值固定)。
换个思路:
考虑一条从根出发的路径 ,那么路径上的神圣度是 ,所以除非 ,否则不变。
但 的话,所有后代都会变成 。所以实际上,整个树被分成若干“区域”,每个区域由该区域中最小的节点编号决定该区域所有节点的神圣度。
标程的思路
标程用了一个贪心构造:
-
先判断合法性:
- 最小总和:当所有节点都直连根时,。如果根 = 1,则所有节点最小值为 1,总和为 ,这是最小可能(因为 )。
实际上最小总和是 :把 1 作为根,所有节点直连根,,且根 1 贡献 1,其它节点 贡献 1,总和 。 - 最大总和:当树是一条链 且根为 时,每个节点 的路径上最小值为自身,总和 。
- 所以必须满足 。
- 最小总和:当所有节点都直连根时,。如果根 = 1,则所有节点最小值为 1,总和为 ,这是最小可能(因为 )。
-
然后尝试构造:
- 设根为 ,,其它节点我们可以分配“增量”。
令 ,表示除了每个节点至少贡献 1 以外,还需要多贡献 的总和。 - 因为每个节点至少贡献 1,现在考虑如何增加。
实际上更直接的做法(标程):
- 先把所有节点贡献设为 1,总和为 ,需要额外总和 。
- 如何增加?
把某个节点 从贡献 1 增加到贡献 ,需要保证它到根的路径上最小值是 ,即路径上所有节点编号 ,且 本身在该路径上。
这等价于把它放在一条所有祖先 的路径上,且它自己是该路径上最小的。 但最简单:让节点 成为某个路径的起点,且挂在 或更大下。
实际上标程用了贪心:从大到小取节点作为链的一部分,每个节点贡献 而不是 1。
- 设根为 ,,其它节点我们可以分配“增量”。
标程的构造细节
标程代码(已给)中:
i64 k = sum - n; vector<i64> ans; i64 curr = 0, nsum = 0; for(i64 i = n - 1; i >= 0; --i) { if(curr == k) break; if(curr + i <= k) { curr += i; ans.push_back(i + 1); nsum += i + 1; } } i64 ct = ans.size(); for(i64 i = 0; i < n - ct; ++i) ans.push_back(1);这里:
- 是需要额外增加的总和。
- 从大到小尝试取 作为链上的节点(因为 从 到 0 对应节点 ),如果当前 就取这个节点作为链的一部分。
- 最后不足 个节点用 1 补齐(表示其余节点直连根)。
然后输出:
- 首先输出根 =
ans[0](即最大那个节点)。 - 然后依次输出链上的边。
- 当遇到 1 时,把剩下的所有未用节点挂在 1 下(形成 1 的子树)。
为什么这样构造有效?
因为:
- 根是最大值 ,链上节点 ,那么每个链上节点的路径最小值 = 自身,贡献自身。
- 挂到 1 下面的节点,路径经过 1,最小值变为 1,贡献 1。
- 其他没被链选中的节点(除了 1 外)如果直接挂在根下,路径只有根和它,最小值 = min(根, 它),因为根最大,所以最小值 = 它自己,贡献自身?等等不对——根 > 它,所以路径上最小是它自己,所以贡献自身。那就不只是 1 了。
这里要仔细看标程:
实际上标程把链上节点挂在前面节点下,剩下的 1 补齐,然后当 1 出现时,把剩下所有未访问的节点(包括本身未访问的)都挂在 1 下,这样它们的路径经过 1,最小值 1。
这样:
- 链上节点:贡献 = 自身编号。
- 1 下面所有节点:贡献 = 1。
- 其它情况(比如不在链上也不在 1 下的节点)不存在,因为所有节点要么在链上要么在 1 下。
总和 = 链节点和 + (剩余节点数) × 1。
链节点和 = 因为初始 个 1,加了 得到。
合法性条件
因此得到:
是充要条件。
最终题解流程
- 如果 或 ,输出 -1。
- 否则:
- 令 。
- 贪心选链:从 往下取,只要选 不超 就加入链。
- 剩余的节点用 1 补齐。
- 输出根 = 链第一个节点。
- 输出链的边。
- 当遇到 1 时,把剩余未输出过的节点(除了 1 自身)都挂到 1 下。
- 结束。
时间复杂度
- 预处理 ,总 不超过 ,可行。
如果你需要,我可以帮你把上述题解整理成最终输出的形式,并附上代码实现。
-
- 1
信息
- ID
- 7140
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 4
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者