交互题选做

001. CF2106G1/G2 Baudelaire
G1:
假设当前已经知道了根在 \(root\),那么可以一遍 dfs 在 \(n\) 次询问中问出所有点的点权。
问题变为在 \(200\) 次操作内获得根的位置。考虑初始随便钦定一个点 \(u\),然后记录 \(Ver\) 为 \(u\) 所有相邻的点构成的点集。那么 \(Ver\) 集合中存在 \(u\) 点的父亲,则:
- \(o_1\) 为当前状态下用 \(1\) 操作询问 \(Ver\) 集合得到的值
- \(o_2\) 为在当前状态下,先翻转一次 \(u\) 结点,然后再用 \(1\) 操作询问 \(Ver\) 集合得到的值
- 那么当且仅当 \(|o_1-o_2|=2|Ver|\) 时,\(Ver\) 集合中不存在 \(u\) 点的父亲,否则 \(Ver\) 集合中存在 \(u\) 点的父亲。
对于上述结论的证明:很显然翻转点 \(u\) 权值,则这个点的权值会增加 \(2\) 或者减少 \(2\),而询问 \(f\) 函数的时候,翻转 \(u\) 点的权值影响了 \(v\) 点的 \(f\) 值,当且仅当 \(u\) 点在 \(v\) 到根节点的简单路径上。因此若 \(|o_1-o_2|=2|Ver|\) 则 \(Ver\) 集中任意一点 \(v\) 到根节点的简单路径都必须经过 \(u\),否则恰好存在一个不经过 \(u\) 的结点,那么这个结点就必然是 \(u\) 的父亲。
直接对所有点询问量级是 \(O(n)\) 的显然 \(200\) 次询问不够。考虑在点集中二分,每一次把点集拆分为左部分和右部分,那么父亲结点若存在,必然要么在左要么在右。若最后剩下一个结点,再单独对其询问检查其是否为 \(u\) 点的父亲即可。
#pragma GCC optimize(3, "Ofast", "inline", "unroll-loops")
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/rope>
#define int long long
using namespace std;
const int inf = 2e18;
using ull = unsigned long long;
template<class _T>
using treap = __gnu_pbds::tree<_T, __gnu_pbds::null_type, less_equal<_T>, __gnu_pbds::rb_tree_tag, __gnu_pbds::tree_order_statistics_node_update>;
using POD = pair<double, double>;
const int N = 1000010;
vector<int> adj[N];
int a[N], res[N], f[N];
vector<int> make(int l, int r) {
vector<int> v;
for (int i = l; i <= r; ++i) v.emplace_back(i);
return v;
}
void dfs(int u, int fa) {
cout << "? 1 1 " << u << endl;
int o; cin >> o;
f[u] = o;
res[u] = f[u] - f[fa];
for (int &v : adj[u])
if (v != fa) dfs(v, u);
}
signed main() {
// freopen("count.in", "r", stdin);
// freopen("count.out", "w", stdout);
// freopen("debug.err", "w", stderr);
// cin.tie(0)->sync_with_stdio(false);
cout << fixed << setprecision(15);
srand(time(0));
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
for (int i = 1; i <= n; ++i) adj[i].clear();
for (int i = 1; i < n; ++i) {
int a, b;
cin >> a >> b;
adj[a].emplace_back(b);
adj[b].emplace_back(a);
}
vector<int> v = make(2, n);
while (v.size() > 1) {
// cout << "size: ";
// cout << v.size() << '\n';
int mid = v.size() >> 1;
vector<int> t;
for (int i = 0; i < mid; ++i) t.emplace_back(v[i]);
cout << "? 1 " << t.size() << ' ';
for (int &i : t) cout << i << ' ';
cout << endl;
int o1; cin >> o1;
cout << "? 2 1" << endl;
cout << "? 1 " << t.size() << ' ';
for (int &i : t) cout << i << ' ';
cout << endl;
int o2; cin >> o2;
int diff = abs(o1 - o2);
int qdif = t.size() * 2;
if (diff != qdif) v.swap(t);
else {
t.clear();
for (int i = mid; i < v.size(); ++i) t.emplace_back(v[i]);
v.swap(t);
}
}
int root = v[0];
cout << "? 1 1 " << root << endl;
int o1; cin >> o1;
cout << "? 2 1" << endl;
cout << "? 1 1 " << root << endl;
int o2; cin >> o2;
int diff = abs(o1 - o2);
int qdif = 2;
if (diff == qdif) root = 1;
dfs(root, 0);
cout << "! ";
for (int i = 1; i <= n; ++i) cout << res[i] << ' ';
cout << endl;
}
return 0;
}
G2:
若在树上剩余部分随机撒一个点当 \(root\) 那么显然没有美丽的性质。因此考虑每一次在树的剩余部分用重心当 \(root\),根据点分治的经典结论拆 \(O(\log n)\) 次重心就可以把树删到一个结点。因此做完了。
#pragma GCC optimize(3, "Ofast", "inline", "unroll-loops")
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/rope>
#define int long long
using namespace std;
const int inf = 2e18;
using ull = unsigned long long;
template<class _T>
using treap = __gnu_pbds::tree<_T, __gnu_pbds::null_type, less_equal<_T>, __gnu_pbds::rb_tree_tag, __gnu_pbds::tree_order_statistics_node_update>;
using POD = pair<double, double>;
const int N = 1000010;
vector<int> adj[N];
int a[N], res[N], f[N];
vector<int> make(int l, int r) {
vector<int> v;
for (int i = l; i <= r; ++i) v.emplace_back(i);
return v;
}
void dfs(int u, int fa) {
cout << "? 1 1 " << u << endl;
int o; cin >> o;
f[u] = o;
res[u] = f[u] - f[fa];
for (int &v : adj[u])
if (v != fa) dfs(v, u);
}
int siz[N], wei[N], rsiz, cen, vis[N];
void getbc(int u, int fa) {
siz[u] = 1, wei[u] = 0;
for (int &v : adj[u])
if (v != fa && !vis[v]) {
getbc(v, u);
siz[u] += siz[v];
wei[u] = max(wei[u], siz[v]);
}
wei[u] = max(wei[u], rsiz - siz[u]);
if (wei[u] <= rsiz / 2) cen = u;
}
signed main() {
// freopen("count.in", "r", stdin);
// freopen("count.out", "w", stdout);
// freopen("debug.err", "w", stderr);
// cin.tie(0)->sync_with_stdio(false);
cout << fixed << setprecision(15);
srand(time(0));
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
for (int i = 1; i <= n; ++i) adj[i].clear(), vis[i] = 0;
for (int i = 1; i < n; ++i) {
int a, b;
cin >> a >> b;
adj[a].emplace_back(b);
adj[b].emplace_back(a);
}
rsiz = n, cen = 1;
while (rsiz > 1) {
vector<int> v;
getbc(cen, 0);
for (int &x : adj[cen]) v.emplace_back(x);
while (v.size() > 1) {
// cout << "size: ";
// cout << v.size() << '\n';
int mid = v.size() >> 1;
vector<int> t;
for (int i = 0; i < mid; ++i) t.emplace_back(v[i]);
cout << "? 1 " << t.size() << ' ';
for (int &i : t) cout << i << ' ';
cout << endl;
int o1; cin >> o1;
cout << "? 2 " << cen << ' ' << endl;
cout << "? 1 " << t.size() << ' ';
for (int &i : t) cout << i << ' ';
cout << endl;
int o2; cin >> o2;
int diff = abs(o1 - o2);
int qdif = t.size() * 2;
if (diff != qdif) v.swap(t);
else {
t.clear();
for (int i = mid; i < v.size(); ++i) t.emplace_back(v[i]);
v.swap(t);
}
}
int root = v[0];
cout << "? 1 1 " << root << endl;
int o1; cin >> o1;
cout << "? 2 " << cen << endl;
cout << "? 1 1 " << root << endl;
int o2; cin >> o2;
int diff = abs(o1 - o2);
int qdif = 2;
if (diff == qdif) root = cen;
if (root == cen) {
dfs(root, 0);
cout << "! ";
for (int i = 1; i <= n; ++i) cout << res[i] << ' ';
cout << endl;
break;
}
vis[cen] = 1;
rsiz = siz[cen];
cen = root;
}
}
return 0;
}

浙公网安备 33010602011771号