Luogu7597 猜树

Arahc 11.0

题面

简化题意

给一个 11 为根的有根树。你可以查询两点距离(消耗 11 代价)或一个点的子树(消耗子树大小的代价)。你需要消耗 40000\leq 40000 代价还原这个树。

n5000n\leq 5000

题解

首先询问每个点到根的距离求 depdep。考虑 dsu on tree,已知 uu 子树和 uu 除了一个儿子 vv 之外的儿子的子树,就能得到 vv 子树。

然而并不能像 dsu 一样找到重儿子——我们并不知道树的结构。考虑随机。

如果一个点的子树最大,那么如果我随机若干个点作为标记点,这个点的子树中就会有期望更多的标记点。这个时候我们令占标记点最多的点为重儿子。

那么应该随机几个呢?最好还是简单为好,为了避免两个点所占的标记点一样多导致冲突,直接只随机一个点就可以了。

这样子的算法非常玄学。但是其复杂度退化的原因就在于我随机的时候并没有找到重儿子。实际测试的时候发现即使我找到的点大部分都是轻儿子,复杂度的退化也并不明显。

再者,如果要卡掉这种随机算法,要从两个方面考虑:一个是加多轻儿子数量,另一个就是让其因为找到轻儿子复杂度退化更加明显。

选择加多轻儿子数量,可以众生平等开完美多叉树,但这样又可以视为每个点都是重儿子,而且如果一个点的儿子个数 >2>2 ,复杂度甚至会比 还要小。就算开的是完美二叉树,他的复杂度也是 log2n\log_2 n 的。显然轻儿子数量越多,重儿子和轻儿子的子树大小差值也会变小,整体效率还是非常可观的。

那么还有一个方法就是让找到轻儿子复杂度的退化加剧,这样做就只能不断加大重儿子的子树大小,让其拉开和轻儿子子树大小的差距。实际上如果重儿子子树大小和轻儿子差距越大,那么每一次随机点我找到重儿子的概率也会越大。考虑到 nn 虽然不大,但是也不是很小,可以估算找到重儿子的次数是非常可观的。

于是我们大概口糊了一下算法的正确性,交上去是可以 AC 的。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include<bits/stdc++.h>
using namespace std;
const int max_n=5050;
inline int ask1(int i,int j){
printf("? 1 %d %d\n",i,j);
fflush(stdout);
int dis;
scanf("%d",&dis);
return dis;
}
inline int ask2(int x){
printf("? 2 %d\n",x);
fflush(stdout);
int size;
scanf("%d",&size);
return size;
}

int n,d[max_n],s[max_n],t[max_n][max_n],hs[max_n],f[max_n];
bool b[max_n];

inline void dfs(int u){
if(!s[u]) return;
int p=t[u][rand()%s[u]+1];
for(register int i=1;i<=s[u];++i){
int v=t[u][i];

if(d[v]!=d[u]+1) continue;

int dis;
if(v==p) dis=0;
else dis=ask1(v,p);
if(d[p]-d[v]==dis){
hs[u]=t[u][i];
break;
}
}

memset(b,0,sizeof(b));
for(register int i=1;i<=s[u];++i){
int v=t[u][i];
if(d[v]!=d[u]+1) continue;
if(v==hs[u]) continue;

s[v]=ask2(v);
int _n=0;
for(register int j=1;j<=s[v];++j){
int x;
scanf("%d",&x);
if(x!=v) t[v][++_n]=x;
b[x]=1;
}
--s[v];
}

for(register int i=1;i<=s[u];++i){
int v=t[u][i];
if(b[v]) continue;
if(v==hs[u]) continue;
t[hs[u]][++s[hs[u]]]=v;
}
for(register int i=1;i<=s[u];++i){
int v=t[u][i];
if(d[v]!=d[u]+1) continue;
f[v]=u;
dfs(v);
}
}

signed main(){
scanf("%d",&n);
for(register int i=2;i<=n;++i){
d[i]=ask1(1,i)+1,
t[1][++s[1]]=i;
}
d[1]=1;
dfs(1);

printf("! ");
for(register int i=2;i<=n;++i) printf("%d ",f[i]);
printf("\n");
fflush(stdout);
return 0;
}
  • 标题: Luogu7597 猜树
  • 作者: Arahc
  • 创建于 : 2021-07-03 08:00:00
  • 更新于 : 2023-03-18 10:53:08
  • 链接: https://arahc.github.io/2021/07/03/【题解】Luogu7597-猜树/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
目录
Luogu7597 猜树