题解 P7595 【猜树】
Legitimity · · 题解
交互题?可以乱搞,有意思
首先分别考虑只用一种询问的做法。
subtask1+2+3(?)+5
考虑询问
这两个都可以在询问子树的时候求出来,具体做法不细讲。
然后是类似拓扑排序的思想:将所有节点按
处理到叶子节点
这样不断向后处理,处理到
为什么?可以自己试着画图推一下,其实非常好理解,这里直接放一下代码:
#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;
}
时间复杂度和询问次数?这里的复杂度和子树大小递减的速度有关,同一层的节点越多,复杂度就越低,比如完全二叉树时,时间复杂度和询问次数为
期望得分
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;
}