1 条题解
-
0
自动搬运
来自洛谷,原作者为

Legitimity
Will there be a happily-ever-after?搬运于
2025-08-24 22:31:23,当前版本为作者最后更新于2021-05-10 21:15:46,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
交互题?可以乱搞,有意思
首先分别考虑只用一种询问的做法。
subtask1+2+3(?)+5
考虑询问 ,把每个节点的子树都搞下来,开这样几个东西:
- 表示当前节点 子树的大小。
- 这是一个 vector 或二维数组 ,表示节点 所有的祖先(不需要按顺序)
这两个都可以在询问子树的时候求出来,具体做法不细讲。
然后是类似拓扑排序的思想:将所有节点按 由小到大排序,前面的若干个 一定是 ,即这些节点是叶子节点。这样从前到后处理,一定是按照从叶子节点向上处理的顺序。
处理到叶子节点 时,把 删除,按照 把 的祖先的 全减一,找到祖先中 最小的节点 ,则 。因为如果 的祖先 不是 的父亲的话,那么 真正的父亲 的子树则一定是 的子树的子集,所以 。
这样不断向后处理,处理到 时, 也一定等于 ,因为它的子树已经在先前的处理中删掉了。
为什么?可以自己试着画图推一下,其实非常好理解,这里直接放一下代码:
#include<bits/stdc++.h> using namespace std; #define rg register #define inf 0x3f3f3f3f #define ll long long inline int read(){ rg int ret=0,f=0;char ch=getchar(); while(!isdigit(ch)){if(ch=='-')f=1;ch=getchar();} while(isdigit(ch)){ret=ret*10+ch-48;ch=getchar();} return f?-ret:ret; } int n,u,siz[2005],fa[2005]; int f[2005][2005],cnt[2005]; int no[2005]; inline bool cmp(int a,int b){ return siz[a]<siz[b]; } signed main(){ n=read(); siz[1]=n; siz[n+1]=inf; //初始化。 for(rg int i=2;i<=n;++i){ printf("? 2 %d\n",i); fflush(stdout); siz[i]=read(); for(rg int j=1;j<=siz[i];++j){ u=read(); if(u==i) continue; f[u][++cnt[u]]=i; } f[i][++cnt[i]]=1; //1 是根节点,也是所有节点的祖先。 no[i]=i; } sort(no+2,no+1+n,cmp); //将节点按 siz 排序,依次处理。 for(rg int g=2;g<=n;++g){ int now=no[g]; fa[now]=n+1; for(rg int i=1;i<=cnt[now];++i){ --siz[f[now][i]]; //因为自己被删掉了,把祖先的 siz 全部减一。 if(siz[f[now][i]]<siz[fa[now]]) fa[now]=f[now][i]; //siz 最小的就是父亲。 } } printf("! "); for(rg int i=2;i<=n;++i){ printf("%d ",fa[i]); } fflush(stdout); return 0; }时间复杂度和询问次数?这里的复杂度和子树大小递减的速度有关,同一层的节点越多,复杂度就越低,比如完全二叉树时,时间复杂度和询问次数为 ,反之,如果为链,每次子树只递减 ,那么时间复杂度和询问次数约为 。数据随机的情况为 。(prufer 序列生成的真随机,下文亦同)
期望得分
subtask1+2+3+4
考虑询问 。
开这样几个东西:
- 表示节点 的深度(定义根节点深度为 )
- 没了
当 且 时,。
(这不用我解释吧……)
那么思路就很明了了,首先询问 到其它节点的距离得出 ,然后每次把已经知道父亲的节点入队(若 则 )。对于对头的节点 ,询问每个 的 节点,若 为 ,则令 ,并把 入队。
代码:
#include<bits/stdc++.h> using namespace std; #define rg register #define inf 0x3f3f3f3f #define ll long long inline int read(){ rg int ret=0,f=0;char ch=getchar(); while(!isdigit(ch)){if(ch=='-')f=1;ch=getchar();} while(isdigit(ch)){ret=ret*10+ch-48;ch=getchar();} return f?-ret:ret; } int n,u,fa[2005],dep[2005]; int q[2005],f,r; signed main(){ n=read(); dep[1]=0; for(rg int i=2;i<=n;++i){ printf("? 1 1 %d\n",i); fflush(stdout); dep[i]=read(); if(dep[i]==1){ fa[i]=1; q[++r]=i; } //处理深度和第一批入队的点。 } while(f<r){ int now=q[++f]; for(rg int i=2;i<=n;++i){ if(dep[i]==dep[now]+1&&!fa[i]){ printf("? 1 %d %d\n",now,i); fflush(stdout); u=read(); if(u==1){ fa[i]=now; q[++r]=i; } } }//对于当前节点找到其所有的儿子并入队。 } printf("! "); for(rg int i=2;i<=n;++i){ printf("%d ",fa[i]); } fflush(stdout); return 0; }时间复杂度和询问次数?因为这题对时间复杂度的要求不高,这里窝偷了个懒,时间复杂度是稳定的 ,但其实可以做到和询问次数同阶的。询问次数则与每一次的节点个数有关,询问次数的总数大约是 。如果是链,询问次数达到最优的 ,如果每次的节点很多,如完全二叉树,那么询问次数将会退化到 以上。随机数据的询问次数也是 左右。
期望得分
接下来是乱搞环节,我们发现 相对于 来说非常小,考虑一些不太正经的解法。
可以按照当前深度的节点数 进行分治,如果大于阈值 ,跑第一种,反之跑第二种,时间复杂度是 。这是一个显然的根号分治, 取 时候达到最优的 ,不过这里的不再是随机数据,针对所有数据都可以做到复杂度不退化。
期望得分 。
但还有另一种简单粗暴的写法,不如根号分治优,就是直接判断整颗树的深度,取一个阈值 ,深的话跑第二种,浅的话跑第一种,即数据分治(
我也不知道 取多少最好。这样的复杂只是度是比单独的两种好,但还是可以被卡掉,但是这题数据水,这样写就能过了。
期望得分 。
代码:
#include<bits/stdc++.h> using namespace std; #define rg register #define inf 0x3f3f3f3f #define ll long long #define vit vector<int>::iterator inline int read(){ rg int ret=0,f=0;char ch=getchar(); while(!isdigit(ch)){if(ch=='-')f=1;ch=getchar();} while(isdigit(ch)){ret=ret*10+ch-48;ch=getchar();} return f?-ret:ret; } int n,u,fa[2005],dep[2005],mxdep,siz[2005]; int f[2005][2005],cnt[2005]; int q[2005],fr,r; int no[2005]; inline bool cmp(int a,int b){ return siz[a]<siz[b]; } inline void work(){ siz[1]=n; siz[n+1]=inf; for(rg int i=2;i<=n;++i){ printf("? 2 %d\n",i); fflush(stdout); siz[i]=read(); for(rg int j=1;j<=siz[i];++j){ u=read(); if(u==i) continue; f[u][++cnt[u]]=i; } f[i][++cnt[i]]=1; no[i]=i; } sort(no+2,no+1+n,cmp); for(rg int g=2;g<=n;++g){ int now=no[g]; fa[now]=n+1; for(rg int i=1;i<=cnt[now];++i){ --siz[f[now][i]]; if(siz[f[now][i]]<siz[fa[now]]) fa[now]=f[now][i]; } } } signed main(){ n=read(); dep[1]=0; for(rg int i=2;i<=n;++i){ printf("? 1 1 %d\n",i); fflush(stdout); mxdep=max(dep[i]=read(),mxdep); if(dep[i]==1){ fa[i]=1; q[++r]=i; } } if(mxdep<=50) //数据分治( work(); //第一种。 else{ //第二种。 while(fr<r){ int now=q[++fr]; for(rg int i=2;i<=n;++i){ if(dep[i]==dep[now]+1&&!fa[i]){ printf("? 1 %d %d\n",now,i); fflush(stdout); u=read(); if(u==1){ fa[i]=now; q[++r]=i; } } } } } printf("! "); for(rg int i=2;i<=n;++i){ printf("%d ",fa[i]); } fflush(stdout); return 0; }
- 1
信息
- ID
- 6515
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 5
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者