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;
}

浙公网安备 33010602011771号