交互题选做

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;
}
posted @ 2025-04-25 17:54  0103abc  阅读(12)  评论(0)    收藏  举报