1 条题解
-
0
我们要给 排序,考虑如何比较两个数 。考察这样的构造:定一个异于 的根 ,连 和 ,对于其他点 连 。若返回的最大独立集不包含 ,则容易比较 的大小关系。否则考虑调整,选择一个异于 的点 ,交换 和 ,再做一次询问。如果此时 依然在最大独立集中,则可以推出 。这是因为 ,如果 ,我们把 从独立集中删去,再把 加入独立集显然可以得到更大的独立集。
发现这个其实可以推广,一次性查询若干个不交的对 的大小关系。方法是类似的:定一个根 和一个占位点 (注意保证 ),连 ,然后对于每个询问对连 和 ,对于其他点 连 。 容易通过 次询问得到。这样我们就用 次询问查询若干个对的大小关系。
考虑给除了 之外的点排序。为了充分利用查询多个对的大小关系这一操作,我们需要一些并行程度较高的排序方法。这里引入奇偶归并排序。
::::info[奇偶归并排序]{open}
奇偶归并排序的核心操作是合并两个长度为 的有序序列 。具体来说:
- 取 中的偶数项元素递归合并,得到长度为 的有序序列 。
- 取 中的奇数项元素递归合并,得到长度为 的有序序列 。
- 交错排列 :。
- 对于每个 ,若 则交换位置。
我们来证明这样合并的正确性。
:::info[正确性证明]
使用 0-1 principle,我们只需证明这样合并可以正确合并所有有序 序列 。设 中有 个 , 中有 个 。设 中有 个 , 中有 个 。显然 ,不妨分类讨论其取值:
- :交错序列已经是有序的了。
- :交错序列中唯一的逆序对为 ,显然这个逆序对会被交换。
因此得证。
:::
接下来我们分析一下操作次数。
设合并两个长度为 的有序序列的操作次数为 ,显然有 ,边界为 ,因此 。
设给长度为 的序列排序的操作次数为 。我们将左右两半的长度为 的序列分别排序,再合并起来。我们发现两边的排序可以并行,因此有 ,边界为 ,于是 。
::::
回到本题来看,不妨把 补成 的整数次幂,则排序的操作次数为 $\dfrac{\lceil\log_2(n-2)\rceil(\lceil\log_2(n-2)\rceil+1)}2$,取 时操作次数为 次。加上最开始找出 的 次询问,我们已经使用了 次询问,还剩下 次询问。
现在我们还需要把 插入到排序好的 个点中。对于单个点 的插入,显然可以直接二分 ,选择异于 的两个新的根节点 ,然后比较 。但是一次二分就需要 次比较,显然无法承受两次二分。容易想到把两次二分并行,每轮进行两次比较,如果 相同则做调整使得它们不同。需要特判 的情况,暴力做即可。
:::info[一些细节]{open}
简单说说上文中提到的调整。为了防止二分死循环,我们实际上要把 或者 像没搜索过的位置调整。以我的代码为例,可以这样实现:
while (lu < ru || lv < rv) { int midu = lu + ru >> 1, midv = lv + rv >> 1; if (lu < ru && lv < rv && midu == midv) { if (midv + 1 < rv) ++midv; else if (midu - 1 >= lu) --midu; } // ... if (...) ru = midu; else lu = midu + 1; if (...) rv = midv; else lv = midv + 1; }但是其实这样调整也可能有问题。存在 的情况,但是此时必然有 ,所以直接让这两次比较分开做就行了。这样至多会产生 次额外的询问。
:::
这部分操作次数至多为 次,可以承受。
这样我们就用最多 次操作解决了本题。
:::success[主要代码]
vector<int> sorting(int N) { n = N, rtu = 0, rtv = 1; vector<array<int, 2>> T; T.push_back({rtu, rtv}); T.push_back({rtu, 2}), T.push_back({2, 3}); for (int i = 4; i < n; ++i) T.push_back({rtu, i}); auto vis = ask_question(T); if (!vis[rtu]) { if (!vis[2]) rtu = 2, rtv = 3; else rtu = 3, rtv = 2; } else swap(rtu, rtv); vector<int> vec; for (int i = 0; i < n; ++i) if (i != rtu && i != rtv) vec.emplace_back(i); int sz = vec.size(); for (int p = 1, t = 1; p < sz; p <<= 1, ++t) for (int q = p; q >= 1; q >>= 1) { int r = q < p ? q : 0; vector<array<int, 2>> E; vector<pii> pairs; for (int i = r; i + q < sz; i += q << 1) for (int j = 0; j < q && i + j + q < sz; ++j) if ((i + j >> t) == (i + j + q >> t)) { E.push_back({vec[i + j], vec[i + j + q]}); pairs.emplace_back(i + j, i + j + q); } if (!E.empty()) { auto res = compare(E, rtu, rtv); for (int k = 0; k < res.size(); ++k) if (!res[k]) swap(vec[pairs[k].first], vec[pairs[k].second]); } } int lu = 0, ru = sz, lv = 0, rv = sz; if (n == 5) { auto get_pos = [&](int x) { int l = 0; while (l < sz) { int rt1 = 0; while (rt1 == l) ++rt1; int rt2 = rt1 + 1; while (rt2 == l) ++rt2; auto res = compare({{x, vec[l]}}, vec[rt1], vec[rt2]); if (res[0]) return l; ++l; } return l; }; lu = get_pos(rtu), lv = get_pos(rtv); } else { while (lu < ru || lv < rv) { int midu = lu + ru >> 1, midv = lv + rv >> 1; if (lu < ru && lv < rv && midu == midv) { if (midv + 1 < rv) ++midv; else if (midu - 1 >= lu) --midu; } int rt1 = 0; while (rt1 == midu || rt1 == midv) ++rt1; int rt2 = rt1 + 1; while (rt2 == midu || rt2 == midv) ++rt2; vector<array<int, 2>> E; int id1 = -1, id2 = -1; if (lu < ru) id1 = E.size(), E.push_back({rtu, vec[midu]}); if (lv < rv && (!~id1 || midu != midv)) id2 = E.size(), E.push_back({rtv, vec[midv]}); auto res = compare(E, vec[rt1], vec[rt2]); if (~id1) res[id1] ? ru = midu : lu = midu + 1; if (~id2) res[id2] ? rv = midv : lv = midv + 1; } } vector<int> res; for (int i = 0; i <= sz; ++i) { if (lu == i) res.emplace_back(rtu); if (lv == i) res.emplace_back(rtv); if (i < sz) res.emplace_back(vec[i]); } return res; }:::
- 1
信息
- ID
- 9665
- 时间
- 5000ms
- 内存
- 2048MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者