Luogu7597 猜树

Arahc 11.0

题面

简化题意

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

n≤5000n\leq 5000。

题解

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

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

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

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

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

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

选择加多轻儿子数量,可以众生平等开完美多叉树,但这样又可以视为每个点都是重儿子,而且如果一个点的儿子个数 >2>2 ,复杂度甚至会比 还要小。就算开的是完美二叉树,他的复杂度也是 log⁡2n\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/SOL-Luogu7597/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
目录
Luogu7597 猜树