P11988 [JOIST 2025] 宇宙怪盗 题解

Description

这是一道交互题。本题中,交互库可能是自适应的。

有一张 \(N\) 个点 \(M\) 条边的无向连通图。点编号 \(0\sim N-1\),边编号 \(0\sim M-1\),第 \(i\)\(0 \leq i \leq M-1\))条边双向连接点 \(U_i\)\(V_i\)

有一把钥匙藏在某一个点上,而有一个宝箱藏在另一个节点上。你需要通过至多 \(300\) 次询问确定钥匙所在的节点编号和宝箱所在的节点编号:

询问

对于 \(i=0,1,\ldots,M-1\),将第 \(i\) 条边设置为单向通行。

  • 具体地,构造长度为 \(M\)\(01\) 序列 \(x_0\sim x_{M-1}\)\(x_i=0\) 表示第 \(i\) 条边从 \(U_i\) 指向 \(V_i\)\(x_i=1\) 表示第 \(i\) 条边从 \(V_i\) 指向 \(U_i\)

交互库会返回,在这张图中,是否能从钥匙所在的节点到达宝箱所在的节点。

你需要用至多 \(70\) 次询问确定钥匙所在的节点 \(A\) 和宝箱所在的节点 \(B\)

\(N\leq 10000,M\leq 15000\)

Solution

先考虑树怎么做。注意到我们如果能够找到任何一个合法的定向方案使得 \(A\) 能到 \(B\),那么把这棵树的拓扑序拿出来就能二分了。因为 \(A\) 此时拓扑序一定在 \(B\) 之前,二分找到最长的前缀 \([1,d]\),让拓扑序数组上 \([1,d]\)\([d+1,n]\) 之间的边反向,如果 \(A\) 还能到 \(B\) 就说明 \(A\)\([d+1,n]\) 里,否则 \(A\)\([1,d]\) 里。对 \(B\) 是同理的。

现在再来考虑怎么找到一组这样的定向方案。

对于菊花的情况,由于 \(A\)\(B\) 至少有一个二进制位不同,所以选择一个二进制位,让这一位为 \(0\) 的边方向指向根,为 \(1\) 的方向指向叶子。再询问将这个图反向的结果,通过 \(2\log N\) 次询问一定能够得到满足条件的方案。

对于普通树,考虑通过点分治将问题变得类似菊花,即钦定 \(A\)\(B\) 的路径经过当前的根 \(x\),我们让 \(x\) 每个儿子子树内的边方向相同,就变成菊花了,然后让点分树同层的 \(x\) 并行做,总次数是 \(O(\log^2N)\)

注意到 \(x\) 是重心,所以 \(x\) 儿子子树最大的大小不超过 \(\frac{sz_x}{2}\),容易发现一定存在 \(x\) 儿子的一个划分方案使得每个集合内的子树大小和在 \(\left[\frac{sz_x}{3},\frac{2sz_x}{3}\right]\) 内,随便找到一个这样的划分,将这两个划分看成两个子树做上面的做法,再对这两个划分分治即可。

总次数为 \(2\log_{\frac{3}{2}}N+2\log_2N\)

Code

#include "thief.h"
#include <bits/stdc++.h>

#ifdef ORZXKR
#include "grader.cpp"
#endif

const int kMaxN = 6e4 + 5, kMaxM = 2e4 + 5;

int n, m, rt, mxd, cnt;
int u[kMaxM], v[kMaxM], sz[kMaxN], mx[kMaxN], dep[kMaxN], real[kMaxN], now[kMaxN];
bool ont[kMaxM], del[kMaxN];
std::vector<std::pair<int, int>> G[kMaxN], T[kMaxN];
std::vector<int> eid[kMaxN];

void add(int u, int v, int id) {
  T[u].emplace_back(v, id), T[v].emplace_back(u, id);
}

bool ask(std::vector<int> perm) {
  static int pos[kMaxN];
  std::vector<int> vec(m);
  for (int i = 1; i <= n; ++i) pos[perm[i - 1]] = i;
  for (int i = 1; i <= m; ++i) vec[i - 1] = (pos[u[i]] > pos[v[i]]);
  return query(vec);
}

bool ask(bool *dir, bool op = 0) {
  std::vector<int> vec(m);
  for (int i = 1; i <= m; ++i) vec[i - 1] = dir[i] ^ op;
  return query(vec);
}

struct Node {
  int deg[kMaxN] = {0}, _deg[kMaxN] = {0};
  std::vector<int> G[kMaxN];
  void add(int u, int v) { G[u].emplace_back(v), ++_deg[v]; }
  std::vector<int> getperm() {
    std::queue<int> q;
    std::vector<int> perm;
    for (int i = 1; i <= n; ++i) {
      deg[i] = _deg[i];
      if (!deg[i])
        q.emplace(i);
    }
    for (; !q.empty();) {
      int u = q.front(); q.pop();
      perm.emplace_back(u);
      for (auto v : G[u]) {
        if (!--deg[v]) q.emplace(v);
      }
    }
    return perm;
  }
  bool query() { return ask(getperm()); }
} gr[100];

void build() {
  static bool vis[kMaxN];
  std::fill_n(vis + 1, n, 0);
  std::function<void(int)> dfs = [&] (int u) {
    vis[u] = 1;
    for (auto [v, id] : G[u]) {
      if (!vis[v]) {
        ont[id] = 1, T[u].emplace_back(v, id), T[v].emplace_back(u, id);
        dfs(v);
      }
    }
  };
  dfs(1);
}

void getsz(int u, int fa) {
  sz[u] = 1, mx[u] = 0;
  for (auto [v, id] : T[u]) {
    if (v == fa || del[v]) continue;
    getsz(v, u);
    sz[u] += sz[v], mx[u] = std::max(mx[u], sz[v]);
  }
}

void getrt(int u, int fa, int tot) {
  mx[u] = std::max(mx[u], tot - sz[u]);
  if (mx[u] < mx[rt]) rt = u;
  for (auto [v, id] : T[u]) {
    if (v == fa || del[v]) continue;
    getrt(v, u, tot);
  }
}

void dfs(int u, int fa, int rt) {
  dep[u] = dep[fa] + 1;
  now[real[u]] = u;
  for (auto [v, id] : T[u]) {
    if (v == fa || del[v]) continue;
    if (id) eid[rt].emplace_back(id);
    dfs(v, u, rt);
  }
}

void solve(int u, int d) {
  static int pid[kMaxN];
  mx[rt = 0] = 1e9, getsz(u, 0), getrt(u, 0, sz[u]);
  u = rt, dep[u] = 1, now[real[u]] = u;
  std::vector<int> vid;
  int tot = 0;
  for (auto [v, id] : T[u]) {
    if (del[v]) continue;
    eid[v] = (id ? std::vector<int>{id} : std::vector<int>{});
    pid[v] = id;
    vid.emplace_back(v), dfs(v, u, v);
    tot += eid[v].size();
  }
  std::sort(vid.begin(), vid.end(), [&] (int i, int j) { return eid[i].size() < eid[j].size(); });
  if (!tot) return;
  int id1 = 0, id2 = 0;
  std::vector<int> e1, e2;
  for (int i = 0, now = 0; i < vid.size(); ++i) {
    now += eid[vid[i]].size();
    if (now >= tot / 3) {
      real[id1 = ++cnt] = real[u];
      if (i + 1 < vid.size()) real[id2 = ++cnt] = real[u];
      for (int j = 0; j <= i; ++j) {
        int v = vid[j];
        for (auto id : eid[v]) e1.emplace_back(id);
        add(id1, vid[j], pid[vid[j]]);
      }
      for (int j = i + 1; j < vid.size(); ++j) {
        int v = vid[j];
        for (auto id : eid[v]) e2.emplace_back(id);
        add(id2, vid[j], pid[vid[j]]);
      }
      // std::cerr << "fuckkk " << u << ' ' << tot << ' ' << now << ' ' << tot - now << '\n';
      break;
    }
  }
  for (auto id : e1) {
    int x = ::u[id], y = ::v[id];
    if (dep[now[x]] > dep[now[y]]) std::swap(x, y);
    gr[2 * d - 1].add(x, y), gr[2 * d].add(y, x);
  }
  for (auto id : e2) {
    int x = ::u[id], y = ::v[id];
    if (dep[now[x]] > dep[now[y]]) std::swap(x, y);
    gr[2 * d - 1].add(y, x), gr[2 * d].add(x, y);
  }
  mxd = std::max(mxd, 2 * d);
  del[u] = 1;
  if (tot == 1) return;
  assert(id1 && id2);
  // std::cerr << "shabi " << u << ' ' << cnt << ' ' << d << ' ' << sz[u] << ' ' << id1 << ' ' << id2 << '\n';
  // std::cerr << e1.size() << ' ' << e2.size() << '\n';
  if (id1) solve(id1, d + 1);
  if (id2) solve(id2, d + 1);
}

void solve(int N, int M, std::vector<int> U, std::vector<int> V) {
  n = N, m = M;
  for (int i = 1; i <= m; ++i) {
    u[i] = U[i - 1] + 1, v[i] = V[i - 1] + 1;
    G[u[i]].emplace_back(v[i], i);
    G[v[i]].emplace_back(u[i], i);
  }
  build();
  cnt = n;
  for (int i = 1; i <= n; ++i) real[i] = i;
  solve(1, 1);
  for (int d = 1; d <= mxd; ++d) {
    // std::cerr << gr[d].query() << '\n';
    if (gr[d].query()) {
      auto perm = gr[d].getperm();
      static int pos[kMaxN];
      for (int i = 1; i <= n; ++i) pos[perm[i - 1]] = i;
      int a = 0, b = 0;
      {
        int L = 1, R = n + 1, res = 1;
        while (L + 1 < R) {
          int mid = (L + R) >> 1;
          std::vector<int> vec(m);
          for (int i = 1; i <= m; ++i) {
            int x = pos[u[i]], y = pos[v[i]];
            if (x < mid || y < mid) vec[i - 1] = (x < y);
            else vec[i - 1] = (x > y);
          }
          if (query(vec)) L = res = mid;
          else R = mid;
        }
        a = perm[res - 1];
      }
      {
        int L = 0, R = n, res = n;
        while (L + 1 < R) {
          int mid = (L + R) >> 1;
          std::vector<int> vec(m);
          for (int i = 1; i <= m; ++i) {
            int x = pos[u[i]], y = pos[v[i]];
            if (x > mid || y > mid) vec[i - 1] = (x < y);
            else vec[i - 1] = (x > y);
          }
          if (query(vec)) R = res = mid;
          else L = mid;
        }
        b = perm[res - 1];
      }
      answer(a - 1, b - 1);
      return;
    }
    // std::cerr << gr[i].ask() << '\n';
  }
}
posted @ 2025-10-09 14:46  下蛋爷  阅读(18)  评论(0)    收藏  举报