Description
这是一道交互题。
交互库有一个隐藏的 个点的树,你需要构造一个长度为 的 的排列 ,满足 。保证 。特殊的,保证树上任意一个结点的度数都不超过 。
你可以向交互库询问不超过 个问题,每个问题形如:
hoursRequired(X, Y):查询树上 到 的简单路径需要经过的边的数量。后面记该操作为 操作。attractionsBehind(X, Y):查询有多少个点 满足 到 的唯一简单路径要经过点 。后面记该操作为 操作。
Solution
先考虑如果已经知道树的形态,如何找出一个符合条件的排列 。
考虑随便钦定一个结点 为树的根结点,然后把 从树上删除,可以得到不超过 个子树,这里记 ,则可以得到 个子树。将这 个子树内的结点分别按照其深度从大到小排序,得到 个不同的集合 。
此时显然可以得到结论:对于任意两个整数 满足 ,均满足 ,,其中 表示 到根结点简单路径上边的数量,即 点在树上的深度。
若此时有 ,则容易想到贪心的让相邻两个点属于不同的 集合里,每次取出和上一个点不属于同一个 集合里的点里深度最深的点。在所有集合内点均被放入序列后,将当前的根节点 插入到序列的末尾。记这 个集合内点的数量分别为 (),则当且仅当 时,上述构造可以给出合法的序列。下面考虑证明该结论:
- 若 ,则无法满足“相邻两点属于不同的 集合”这一条件。
- 否则,在每个 集合里深度更深的点一定比深度更浅的点在序列中的位置更靠前,因此对于相邻的三个位置 ,一定可以满足 。
特判掉 的情况,当 时一定可以找出一个点 使得其得到的 满足 且 。具体的,让 为树的重心结点即可。当 时一定有 ,且根据重心的性质可知其不存在一个子树的大小超过 ,可以满足条件 。
此时考虑需要在交互过程中询问哪些信息可以实现上面给出的算法。容易发现,上述算法需要利用到的信息有:
- 树的任意一个重心。
- 树上点到重心的距离。
- 树上每个点分别属于重心哪个儿子的子树。
1. 找出树的任意一个重心
注意到 询问得出的结果其实就是树钦定根结点为 时, 所在子树的结点数量。因此考虑下面的交互策略:
- 对每个 ,询问 即以 为根时, 结点所在子树的结点数量。
- 若不存在 使得 ,则 号结点就是树的重心。
- 否则,在所有满足 的位置 里,最小的 对应的结点 必然是树的一个重心。
下面考虑证明该策略正确:
- 若不存在 使得 ,则每个非 号结点往父亲方向的连通块大小都超过了 ,不能成为树的重心结点,因此 号结点一定是树的重心。
- 考虑对于两个点 而言,她们的子树要么是包含挂关系,要么没有交。
- 因此若两个点的子树大小都超过了 ,则这两个点的子树一定是呈包含关系,因此所有满足 的 在树上必然是一条从根结点出发向下的链。找到最深的结点(即 最小的)满足 ,则 点所有儿子结点的子树大小一定都不超过 , 点向父亲方向的连通块大小也不超过 ,因此 一定是树的一个重心。
- 因此该策略被证明为正确。
2. 树上点到重心的距离
记 为树的一个重心,则直接对每个 ,询问 就可以得到 也就是 到重心的距离了。
3. 树上每个点分别属于重心哪个儿子的子树
如果此时可以找到 个点 满足 分别处于 结点全部 个不同的子树中,则对于其余的结点 ,判定 结点位于 个子树中的哪一个,只需要判定是否有 即可。
而找出这 个代表点的一个简单方法就是直接取所有 的结点 。容易证明这些点移动属于不同的子树。
4. 处理答案
直接按照前面给出的步骤处理答案即可。容易证明该算法符合操作次数限制。
Code
一直 wa 两个点实在调不出来了最后参考了第一篇题解的实现。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1000010;
const int mod = 998244353;
constexpr inline void add(int &x, int v) { x = (x + v) % mod; }
constexpr inline void sub(int &x, int v) { x = (x + mod - v) % mod; }
int s[N], bel[N], dep[N];
#undef int
int hoursRequired(int, int);
int attractionsBehind(int, int);
vector<int> createFunTour(int n, [[maybe_unused]] int Q) {
#define int long long
#define A hoursRequired
#define B attractionsBehind
if (n == 2) return vector<signed>{0, 1};
int rt = 0, mi = n + 1;
s[0] = n;
for (int i = 1; i < n; ++i) s[i] = B(0, i);
for (int i = 0; i < n; ++i)
if (s[i] > n / 2 && s[i] < mi) mi = s[i], rt = i;
for (int i = 0; i < n; ++i) bel[i] = dep[i] = 0;
for (int i = 0; i < n; ++i)
if (i != rt) dep[i] = A(rt, i);
vector<int> vk;
for (int i = 0; i < n; ++i)
if (dep[i] == 1) vk.emplace_back(i);
assert(1 < vk.size() && vk.size() < 4);
for (int i = 0; i < n; ++i) bel[i] = -1;
for (int i = 0; i < vk.size(); ++i) bel[vk[i]] = i;
for (int i = 0; i < n; ++i) if (dep[i] > 1) for (int j = 0; j < vk.size(); ++j)
if (j + 1 == vk.size() || dep[i] + dep[vk[j]] != A(i, vk[j])) {
bel[i] = j;
break;
}
for (int i = 0; i < vk.size(); ++i) bel[vk[i]] = i;
vector<pair<int, int>> buc[3];
for (int i = 0; i < n; ++i)
if (i != rt) buc[bel[i]].push_back({dep[i], i});
for (int i = 0; i < vk.size(); ++i) sort(buc[i].begin(), buc[i].end());
vector<signed> res;
auto merge = [&]() {
while (buc[2].size())
buc[1].push_back(buc[2].back()), buc[2].pop_back();
sort(buc[1].begin(), buc[1].end());
};
function<void()> solve_2 = [&]() {
while (buc[0].size() || buc[1].size()) {
auto now = buc[0].back();
buc[0].pop_back();
res.emplace_back(now.second);
buc[0].swap(buc[1]);
} res.emplace_back(rt);
};
auto C = [&](int a, int b, int c) -> int {
return a + b + c - max({a, b, c});
};
function<void()> solve_3 = [&]() {
if (buc[0].size() == n / 2) {
merge();
if (buc[0].size() < buc[1].size()) buc[0].swap(buc[1]);
solve_2();
return;
}
int ok = 0;
while (1) {
int ne = -1;
for (int i = ok; i < 3; ++i) {
if (buc[i].empty()) continue;
if (ne == -1 || buc[i].back() >= buc[ne].back()) ne = i;
} ok = 1;
res.emplace_back(buc[ne].back().second);
auto tmp = buc[ne].back();
buc[0].swap(buc[ne]), buc[0].pop_back();
if (max({buc[0].size(), buc[1].size(), buc[2].size()}) == C(buc[0].size(), buc[1].size(), buc[2].size())) {
int now = 0;
if (buc[0].size() < buc[1].size()) buc[0].swap(buc[1]), now = 1;
if (buc[0].size() < buc[2].size()) buc[0].swap(buc[2]), now = (!now ? 2 : now);
if (now && !buc[((now - 1) ^ 1) + 1].empty() && tmp < buc[((now - 1) ^ 1) + 1].back())
res.emplace_back(buc[((now - 1) ^ 1) + 1].back().second), buc[((now - 1) ^ 1) + 1].pop_back();
merge(); if (!now) buc[0].swap(buc[1]); solve_2();
return;
}
}
};
if (vk.size() == 2) {
if (buc[0].size() < buc[1].size()) buc[0].swap(buc[1]);
solve_2();
} else {
if (buc[0].size() < buc[1].size()) buc[0].swap(buc[1]);
if (buc[0].size() < buc[2].size()) buc[0].swap(buc[2]);
solve_3();
} return res;
}
喜欢的话,留下你的评论吧~