1 条题解
-
0
题意简述
有一个森林,每次可以从任意一棵树中删除一个子树,进行任意多次,直到所有树被完全删除。每次删除的子树大小是某个正整数。求所有删除子树大小的按位或的最大值。
转化
对于一棵大小为 的树,我们可以通过若干次操作将其完全删除,每次删除一个子树。这些子树的大小构成一个正整数序列,其总和为 。因此,问题转化为:有 个整数 (每棵树的大小),你需要将每个 拆分成若干个正整数之和,使得所有正整数的按位或最大。求该最大 OR。
贪心求解
我们可以按从大到小的顺序处理这些整数。设当前答案为 (初始为 )。对于当前的 (一棵树的大小),从高位向低位扫描(由于 ,扫描 到 位即可):
- 若 的当前位为 ,跳过。
- 若 的当前位为 ,直接将 的这一位置为 (即 )。
- 若 的当前位已经是 ,且 的这一位也是 ,这意味着我们可以将 的这一位“拆掉”,并用剩余部分将 的所有低于该位的位全部变成 。此时执行
ans |= (1 << h) - 1,并停止处理当前 (跳出循环)。
遍历完所有 后, 即为最大 OR。
正确性简述
- 当 的某一位已经是 而 的该位也是 时,我们不能通过直接 OR 获得更大收益(因为该位已置 )。但我们可以利用 来“补充”更低的位。因为 可以被任意拆分,我们可以将其拆成 和 。其中 对答案无贡献,而剩余部分 ,它的二进制表示完全在 位以下。因此我们可以通过这次拆分将所有低于 的位全部变成 。这正是代码中
ans |= (1<<h)-1的含义。 - 按从大到小处理保证了贪心选择不会降低最终结果的位数。
- 这样每个数至多贡献一次“低位填满”操作,整体复杂度 。
为什么树的具体结构无关?
对于任意一棵有根树,我们总能通过选择合适的删除顺序,获得任意一种将 拆分成若干个正整数的方案。因此只需保留树的大小,忽略父节点序列。算法直接读取每棵树的大小即可。
时间复杂度
总节点数 ,加上排序 ,总复杂度 ,在时限内足以通过。
参考代码
#include <bits/stdc++.h> using namespace std; void solve() { int k; cin >> k; vector<int> a(k); for (int i = 0; i < k; ++i) { cin >> a[i]; // 忽略树的结构,直接跳过父节点 for (int j = 0; j < a[i] - 1; ++j) { int trash; cin >> trash; } } sort(a.begin(), a.end(), greater<>()); int ans = 0; for (auto x : a) { for (int h = 23; h >= 0; --h) { int ca = ans >> h & 1, cx = x >> h & 1; if (cx == 0) continue; if (ca == 0) ans |= 1 << h; else { ans |= (1 << h) - 1; break; } } } cout << ans << '\n'; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) solve(); }
- 1
信息
- ID
- 6920
- 时间
- 2000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者