CF1158E

题意:有一颗 $n \leq 10^3$ 个点的有标号无根树,你需要通过不超过 $80$ 次询问确定树的形态。

每次询问可以对每一个点指定一个权值 $v_i$ ,并把所有满足 $d(i,j) \in [1,v_i]$ 的点 $j$ 染色, $d(i,j)$ 表示树上点 $i$ 到点 $j$ 路径边数。

每次交互库返回每个点是否被染色,询问之间互不影响(就是这一次询问被染色的点不会保留到下一次询问)。

首先以点 $1$ 为根,求出每一个点的深度。

如果知道所有深度为 $d$ 的点,然后令它们的权值均为 $k$ ,则可以得到所有深度在 $[d-k,d+k]$ 中的点。

由于深度为 $0$ 的点只有 $1$ 号点,所以可以用两次询问得知确定深度的所有点。

假设已知所有点深度除 $2^k$ 下取整的值,即 $\lfloor\frac{dep_i}{2^k} \rfloor$ 。

以及所有深度是 $2^k$ 倍数的点,则再用 $4$ 次询问可以确定:

$(1).$ 所有的 $\lfloor\frac{dep_i}{2^{k-1}} \rfloor$ 。

$(2).$ 所有深度是 $2^{k-1}$ 倍数的点。

就是将深度是 $2^k$ 的倍数的点分成两类,一类深度是 $2^{k+1}$ 的倍数,一类不是。

将一类点权值全部设为 $2^{k-1}-1$ ,然后没有标记的点深度范围已知,结合之前信息可以得出 $(1)$ 。

分两类是为了让不同点互不干扰。

再将点权设为 $2^{k-1}$ ,结合之前未被标记的点可得 $(2)$ 。

不理解自己画个图看看就好了,实在不行看代码吧。

已知每个点的深度之后,考虑求出每个点父亲的编号。

先考虑解决最高深度为 $3$ 的子问题:按位考虑。

考虑把所有编号二进制第 $i$ 位为 $1$ 且深度为 $2$ 的点权值设为 $1$ ,则可以确定每个深度为 $3$ 的点父亲编号二进制第 $i$ 位。

这样求 $\log$ 次即可解决子问题,正解将多个不同深度的点放在一起处理即可(为了是同组点间互不干扰,选择按深度模 $3$ 分组)。

求深度 $4\log$ 次操作,求父亲编号 $3\log$ 次操作,总共 $7\log$ 次,可以通过本题。

#include<cstdio>
#include<algorithm>
using namespace std;
int n,L=1,dis[1001];
int dep[1001],fa[1001];
bool final[1001],have[1001];
void query(void)
{
    putchar('?');
    for(int i=1;i<=n;i++)
        printf(" %d",dis[i]);
    putchar('\n');
    fflush(stdout);
    for(int i=1;i<=n;i++){
        char c;
        scanf(" %c",&c);
        have[i]=c-'0';
    }
    return;
}

void update(int d,int rem,int opt)
{
    for(int i=1;i<=n;i++)
        if(final[i]&&(dep[i]/d/2)%2==rem)
            dis[i]=d-opt;
        else dis[i]=0;
    query();
    for(int i=1;i<=n;i++){
        if(final[i]||dep[i]/d/2%2!=rem)
            continue;
        if(opt==1&&!have[i])
            dep[i]+=d;
        if(opt==0&&have[i]&&dep[i]/d%2==1)
            final[i]=1;
    }
    return;
}
bool vis[1001];
void getdep(int lim)
{
    for(int bit=lim>>1;bit>1;bit>>=1){
        update(bit,0,1); update(bit,1,1);
        update(bit,0,0); update(bit,1,0);
    }
    for(int i=1;i<=n;i++)
        if(!final[i])
            dep[i]++;
    return;
}

void solve(int rem)
{
    for(int w=1;w<=n;w<<=1){
        for(int i=1;i<=n;i++)
            if(i/w%2==1&&dep[i]%3==rem)
                dis[i]=1;
            else dis[i]=0;
        query();
        for(int i=1;i<=n;i++)
            if(dep[i]%3==(rem+1)%3&&have[i])
                fa[i]+=w;
    }
    return;
}
int main(void)
{
    scanf("%d",&n);
    while(L<n) L<<=1;
    final[1]=1; getdep(L);
    solve(0); solve(1); solve(2);
    printf("!\n");
    for(int i=2;i<=n;i++)
        printf("%d %d\n",fa[i],i);
    fflush(stdout);
    return 0;
}
View Code
posted @ 2021-09-22 22:20  Miracle_Creater  阅读(106)  评论(0)    收藏  举报