Loading

CF2106G 解题报告:点分治浅谈

包含本题的交互题单

首先注意到题目给了 \(n+200\) 次询问,启发我们用 \(200\) 次找到根节点,剩下 \(n\) 次直接询问每个点。后者太简单了,思考前者。

考虑转化一下根的判定:询问 \(u\) 所有邻点的集合 \(S\) 得到 \(x\),然后 toggle 一下 \(u\) 的值,询问 \(S\) 得到 \(y\)\(u\) 是根节点当且仅当 \(|x-y|=2|S|\)

直接考虑选一个点,然后判定根在这个点哪个方向。考虑推广刚刚的判定:如果 \(S\) 的一个子集 \(T\) 上述条件,那么就说名 \(T\) 中没有通往根节点的道路。

于是我们直接对 \(S\) 二分前缀,就能找到那个包含根节点的子树是第几个了。

那如何取 \(u\) 呢?

每次选剩下子树的重心就行,点分治。

所以你用了 \(O(\log^2 n)\) 次检查,查询次数是这个 \(3\) 倍,没人告诉你会跑满,也跑不满,看你谷讨论区老哥证明,最多只会用 \(171\)

所以说啊,就算是交互题没思路的时候也不要把操作限的太死,说不定跑不到上界呢?

话说,二分也是一种分治是吧,那么通过点分治来找符合条件的点是不是可以看成树上的二分,然后点分治找 \(k\) 长链是不是可以看成 CDQ 呢?

所以,这种技巧应该和二分一样记着。看看适用条件:想找一个点,且可以快速查询它是不是在某一个连通块里。

#include <bits/stdc++.h>
#define int long long
using namespace std;
int ask(vector<int> v){
  cout << "? 1 " << v.size() << " ";
  for(int x: v) cout << x << " ";
  cout << endl;
  int res; cin >> res; return res;
}
bool chk(int u, vector<int> S){
  int fst = ask(S);
  cout << "? 2 " << u << endl;
  int snd = ask(S);
  return abs(fst - snd) == 2 * S.size();
}
vector<int> gen(vector<int> S, int x){
  vector<int> res;
  for(int i = 1; i <= x; i ++) res.push_back(S[i]);
  return res;
}
const int N = 1e3 + 7;
int n, vis[N], cur, sum;
int sz[N], mx[N], p, ans[N];
vector<int> g[N];
void cal(int u, int pre){
  ans[u] = ask({u});
  for(int v: g[u]){
    if(v != pre) cal(v, u);
  }
  ans[u] -= ans[pre];
}
void get(int u, int pre){
  sz[u] = 1, mx[u] = 0;
  for(int v: g[u]){
    if(v == pre || vis[v]) continue;
    get(v, u);
    sz[u] += sz[v];
    mx[u] = max(mx[u], sz[v]);
  }
  mx[u] = max(mx[u], sum - sz[u]);
  if(mx[u] < mx[p]) p = u;
}
void solve(){
  cin >> n;
  for(int i = 1; i <= n; i ++){
    g[i].clear();
    vis[i] = 0;
  }
  sum = n;
  for(int i = 1; i < n; i ++){
    int u, v; cin >> u >> v;
    g[u].push_back(v);
    g[v].push_back(u);
  }
  mx[0] = 1e18, cur = 1;
  int rt = -1;
  while(sum > 1){
    p = 0, get(cur, 0);
    vector<int> d(1, 0);
    for(int v: g[p]){
      if(!vis[v]) d.push_back(v);
    }
    int l = 0, r = d.size();
    while(l + 1 < r){
      int mid = (l + r) >> 1;
      if(chk(p, gen(d, mid))) l = mid;
      else r = mid;
    }
    if(r == d.size()){
      rt = p; break;
    }
    vis[p] = 1, cur = d[r];
    int pres = sum;
    for(int v: d){
      if(v == cur) continue;
      if(sz[v] < sz[p]) sum -= sz[v];
      else sum -= (pres - sz[p]);
    }
    sum --;
    if(sum == 1) rt = cur;
  }
  cal(rt, 0);
  cout << "! ";
  for(int i = 1; i <= n; i ++) cout << ans[i] << " ";
  cout << endl;
}
signed main(){
  ios::sync_with_stdio(0), cin.tie(0);
  int t; cin >> t;
  while(t --) solve();
  return 0;
}
posted @ 2026-04-23 18:40  Trent900  阅读(29)  评论(0)    收藏  举报