题解:CF2106G2 Baudelaire (hard version)

由 G1 可知,只要能确定根节点的位置,就能够用 nnn 次操作获得答案。因此,本题就需要在不超过 200200200 次的询问中获得根节点的位置。

设当前点为 uuu,与 uuu 相邻的点组成集合 SSS。若当前询问集合 QQQ 满足,Q⊂SQ \subset SQS,则可以进行操作:

  1. 111 操作询问 QQQ,获得答案 sxsxsx
  2. 进行 222 操作,翻转 uuu
  3. 再次用 111 操作询问 QQQ,获得答案 sysysy

可以发现,若 ∣sx−sy∣=2×∣Q∣|sx - sy| = 2 \times |Q|sxsy=2×Q,则 QQQ 中不存在 uuu 的父亲节点。

利用这个性质,可以先找到以 uuu 为子树的重心 ccc,然后将 ccc 所有相邻的点加入集合中,通过二分法找到哪一个子结点与根距离最近。具体来说,分为以下情况:

  • ccc 已经不存在相邻点–此时 ccc 即为所求。
  • 询问 ccc 所有相邻的点且符合以上条件-- ccc 为所求。
  • 剩下的情况就进行二分操作。

综上所述,至多需要约 n+3×⌈log⁡(n)⌉n + 3 \times \lceil\log (n)\rceiln+3×log(n)⌉ 次询问之后就能找到根节点。最后进行一次 dfs 并询问即可求出答案。

#include <bits/stdc++.h>
#define init(x) memset (x,0,sizeof (x))
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
#define pii pair <int,int>
using namespace std;
const int MAX = 1e5 + 5;
const int MOD = 1e9 + 7;
inline int read ();
int t,n;
int main ()
{
    t = read ();
    while (t--)
    {
        n = read ();
        int tot = 0,c = 0;
        vector <int> ans (n + 1),sz (n + 1),w (n + 1),vis (n + 1,0);
        vector <vector <int>> ve (n + 1);
        for (int i = 1;i < n;++i) 
        {
            int u = read (),v = read ();
            ve[u].push_back (v);ve[v].push_back (u);
        }
        auto query1 = [&] (vector <int> p) -> int
        {
            printf ("? 1 %d ",p.size ());
            for (auto v : p) printf ("%d ",v);
            puts ("");fflush (stdout);
            return read ();
        };
        auto query2 = [&] (int x) -> void {printf ("? 2 %d\n",x);fflush (stdout);};
        auto dfs1 = [&] (auto self,int u,int fa) -> void
        {
            sz[u] = 1;
            for (auto v : ve[u])
            {
                if (v == fa || vis[v]) continue;
                self (self,v,u);
                sz[u] += sz[v];
            }
        };
        auto dfs2 = [&] (auto self,int u,int fa,int pre) -> void
        {
            ans[u] = query1 ({u}) - pre;
            for (auto v : ve[u])
            {
                if (v == fa) continue;
                self (self,v,u,pre + ans[u]);
            }
        };
        auto cen = [&] (auto self,int u,int fa) -> void
        {
            sz[u] = 1;w[u] = 0;
            for (auto v : ve[u])
            {
                if (v == fa || vis[v]) continue;
                self (self,v,u);
                sz[u] += sz[v];
                w[u] = max (w[u],sz[v]);
            }
            w[u] = max (w[u],tot - sz[u]);
            if (w[u] <= tot / 2) c = u;
        };
        auto ask = [&] (vector <int> p) -> pii
        {
            int sx = query1 (p);query2 (c);int sy = query1 (p);
            return {sx,sy};
        };
        auto solve = [&] (auto self,int u) -> int // 找 rt
        {
            dfs1 (dfs1,u,u);tot = sz[u];
            cen (cen,u,u);vis[c] = 1;
            vector <int> g;
            for (auto v : ve[c])
                if (!vis[v]) g.push_back (v);
            if (g.empty ()) return c;//已经找到 rt
            int l = 0,r = g.size () - 1,res = 0;
            auto [sx,sy] = ask (g);
            if (abs (sx - sy) == 2 * g.size ()) return c;//重心即为 rt
            while (l <= r)
            {
                int mid = (l + r) >> 1;
                vector <int> tmp;
                for (int i = 0;i <= mid;++i) tmp.push_back (g[i]);
                auto [sx,sy] = ask (tmp);
                if (abs (sx - sy) == 2 * tmp.size ()) l = mid + 1;
                else res = mid,r = mid - 1;
            }
            return self (self,g[res]);
        };
        dfs2 (dfs2,solve (solve,1),0,0);
        printf ("! ");
        for (int i = 1;i <= n;++i) printf ("%d ",ans[i]);
        puts ("");fflush (stdout);
    }
    return 0;
}
inline int read ()
{
    int s = 0;int f = 1;
    char ch = getchar ();
    while ((ch < '0' || ch > '9') && ch != EOF)
    {
        if (ch == '-') f = -1;
        ch = getchar ();
    }
    while (ch >= '0' && ch <= '9')
    {
        s = s * 10 + ch - '0';
        ch = getchar ();
    }
    return s * f;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值