1 条题解
-
0
Catch the Mole (CF1990E2 Hard Version / Bonus) 题解
本篇题解解决了出题人提出的 100 次询问加强版(Bonus)。
题意
这是一道交互题。
给出一棵 个点的树,一个隐藏的鼹鼠在其中一个节点。
每次你可以以
? x的格式询问一个节点 。- 如果返回 ,说明鼹鼠不在以 为根的子树中。随后,如果鼹鼠不在根节点,那么他会往父节点移动一次。
- 如果返回 ,说明鼹鼠在以 为根的子树中。
你需要定位这个鼹鼠,以
! x的格式回答,说明询问结束之后鼹鼠在 节点。Easy Version:询问次数不超过 次。
Hard Version:询问次数不超过 次。
Bonus:询问次数不超过 次。
Hint
- 如果一次询问的结果为 ,你可以删除这棵子树。
- 如果询问对象是一个叶节点,当返回为 时,答案为该节点;否则,鼹鼠将会向上移动一格。
- 如果一棵子树的返回为 ,那么我们可以通过 Hint 2 把它赶出这个子树。
Hard Version 解法
(为方便描述,下面将"以 为根的子树"简称作"子树 ")
首先,如果我们已经确定了鼹鼠一定在从根节点开始的一条链上,那么通过二分查找,可以用最多 次询问找到这只鼹鼠。
于是,问题变为用 次询问如何将其确定在这样一条链上。
定义节点 到根的距离为 ,到子树 内最深的节点的距离为 。
通过 Hint 3,不难想到如果我们已经确定了鼹鼠在子树 里,那么我们可以通过 次操作将其赶出这棵子树,使其在一条满足要求的链上。
考虑每次查询一个 使得 ,如果返回 就删除子树 ,否则用 次将鼹鼠赶到一条满足要求的链上。如果没有这样的子树,即所有 都小于 ,那么直接用 次使其到达根节点。
不难发现如果删除的话,每次会删除至少 个节点,所以最多只能删 次,所以询问次数最多为 ,不超过 ,足以通过 Hard Version。
Bonus 解法(100 次询问)
我们需要通过不超过 次操作将鼹鼠赶到一条满足要求的链上。
每次,我们找一个点 ,使得以 为根的子树为一条链,而且以 的父亲为根的子树不为一条链。
这样的好处在于,如果我们确定鼹鼠在子树 里,我们无需将其赶出这棵子树,可以直接在这个纯净链上二分。
如果鼹鼠不在这棵子树里,我们删除这棵子树和所有的叶子节点,然后重复以上操作即可。
不过,这种做法下,时间复杂度为 ,高达 。于是,我们需要优化找符合要求的点 的过程。
考虑找一个叶子节点倍增往上跳,用线段树维护子树大小来确定子树 是否是一条链,这样最终时间复杂度为 ,足以通过本题。
代码
#include<bits/stdc++.h> #define cint const int #define iint inline int #define ll long long #define cll const long long #define ill inline long long using namespace std; iint read() { int num = 0; char ch = getchar(); while (ch < '0' || ch > '9') ch = getchar(); while (ch >= '0' && ch <= '9') { num = (num << 1) + (num << 3) + (ch - '0'); ch = getchar(); } return num; } int n; int head[5001], f[5001][13], a[5001], deg[5001], dfn[5001], l[5001], r[5001]; struct edge { int to, nxt; } e[10000]; int tot; struct que { int a[5001], h, t; inline void clear() { h = 0, t = -1; } inline void push(cint x) { a[++t] = x; } inline void pop() { ++h; } iint front() { return a[h]; } iint size() { return t - h + 1; } inline bool empty() { return t < h; } } leaf; bool query(cint x) { printf("? %d\n", x); fflush(stdout); return read(); } inline void add_edge(cint u, cint v) { e[++tot] = edge{v, head[u]}; head[u] = tot; e[++tot] = edge{u, head[v]}; head[v] = tot; } void dfs(cint now) { l[now] = dfn[now] = ++tot; for (int i = head[now]; i; i = e[i].nxt) { if (e[i].to == f[now][0]) continue; ++deg[now]; f[e[i].to][0] = now; dfs(e[i].to); } r[now] = tot; } inline void init() { for (int k = 1; k <= 12; ++k) for (int i = 1; i <= n; ++i) f[i][k] = f[f[i][k-1]][k-1]; leaf.clear(); for (int i = 1; i <= n; ++i) if (!deg[i]) leaf.push(i); } namespace A { namespace T { struct node { int l, r, sum; } t[20001]; int idx[5001]; void Build(cint p, cint l, cint r) { t[p].l = l; t[p].r = r; if (l == r) { t[p].sum = 1; idx[l] = p; return; } cint mid = (l + r) >> 1; Build(p << 1, l, mid); Build(p << 1 | 1, mid + 1, r); t[p].sum = t[p << 1].sum + t[p << 1 | 1].sum; } void build() { Build(1, 1, n); } inline void update(cint x) { for (int i = idx[x]; i; i >>= 1) --t[i].sum; } int Ask(cint p, cint l, cint r) { if (t[p].l > r || t[p].r < l) return 0; if (t[p].l >= l && t[p].r <= r) return t[p].sum; return Ask(p << 1, l, r) + Ask(p << 1 | 1, l, r); } int ask(cint l, cint r) { return Ask(1, l, r); } } iint size(cint x) { return T::ask(l[x], r[x]); } iint find(int x) { int len = 1; for (int i = 12; i >= 0; --i) { if (f[x][i] == 0) continue; if (size(f[x][i]) == len + (1 << i)) { len += (1 << i); x = f[x][i]; } } return x; } inline bool check() { return query(find(leaf.front())); } inline void del() { for (int i = leaf.front(); ; i = f[i][0]) { --deg[i]; if (deg[i] > 0) break; T::update(dfn[i]); } leaf.pop(); for (int i = leaf.size(); i; --i) { cint x = leaf.front(); leaf.pop(); --deg[f[x][0]]; T::update(dfn[x]); if (!deg[f[x][0]]) leaf.push(f[x][0]); } } } namespace B { int find(cint l, cint r) { cint mid = (l + r) >> 1; if (l >= r) return a[mid]; if (query(a[mid])) return find(l, mid); if (a[r] == 1) { if (mid + 1 == r) return 1; return find(mid + 2, r); } return find(mid + 2, r + 1); } void solve() { int x = leaf.front(); tot = 0; while (x) { a[++tot] = x; x = f[x][0]; } printf("! %d\n", find(1, tot)); fflush(stdout); } } void solve() { n = read(); for (int i = 1; i <= n; ++i) { head[i] = 0; deg[i] = 0; } tot = 0; for (int i = 1; i < n; ++i) add_edge(read(), read()); tot = 0; dfs(1); init(); A::T::build(); while (!A::check()) A::del(); B::solve(); } int main() { int T = read(); while (T--) solve(); return 0; }
- 1
信息
- ID
- 6909
- 时间
- 8000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者