「EZEC-8」猜树 加强版 题解
题目大意
P7597 「EZEC-8」猜树 加强版
Solution
鲜花:
第二次做交互题,第一次太久远了,发现这种题目还挺有意思的。
感觉这道题不是特别难,虽然不是自己想出来的,但是感觉很容易懂欸。
正解:
首先花费 $n-1$ 次操作一搞到每个节点的深度。
然后想一个事情,这个题目要求我们在 $O(n\log n)$ 内完成,可是一棵树怎么做到 $O(n\log n)$ 呢?受到重链剖分、dsu on tree 的启发,我们联想到可以找每个节点的重儿子。
找他有什么用呢?再想另一个事:如果节点 $u$ 的兄弟节点都已经确定他们的子树内有什么节点了,并且已知节点 $u$ 父亲子树内的所有节点,那么显然剩下的点就是 $u$ 的子树内的节点。
我们可以用操作二来获取某点子树信息。而搭配刚刚思考的内容,我们可以省下一个节点的询问。
我们当然希望这个节点的子树大小越大越好,这样就可以省下很多花费。所以自然而然的想到找重儿子。因为 $\sum siz_i-siz_{heavyson_i}$ 是 $O(n\log n)$ 级别的。
现在我们怎么从一个不清楚结构的树内找一个重儿子呢?
所以我们要用上万能的随机化!
因为重儿子的子树大小是他的兄弟节点中最大的,所以我们随机到一个点是重儿子的子孙的概率是最大的。
问题是随机几个节点呢?随机一个就好了。
我们来细嗦一下随机的情况:
首先大概率是随机到了重儿子的子孙,这样我们可以找到正确的重儿子,时间复杂度有保证。
其次是小概率没随机到重儿子,相当于我们搞混了轻重儿子。可是时间复杂度的退化需要重儿子特别特别“重”,节省掉的花费非常非常“少”,唯有这样才会退化十分严重。可是这种情况我们大概率随机到的是正确的重儿子,而不是轻儿子。
总结一下
如果想卡我随机化错误(搞混轻重儿子),他只需要将我错误的代价搞得很大很大(让重儿子尽可能重),可是这个代价越大,我就越不会犯错误(重儿子越重,我随机到的点是他的子孙的可能越大);如果他把轻重儿子子树大小搞得差不多,那我也不需要那么严谨的区分轻重儿子,因为我的目的只是达到 $O(n\log n)$ 量级。
双倍经验(其实是弱化版):P7595 「EZEC-8」猜树
代码
小知识:为什么我没有用 fflush(stdout)
,那是因为 endl
内部就有重载缓冲区。这也是 cout<<endl
会比 cout<<"\n"
稍慢的原因。
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
|
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5e3+5;
int n,dep[N],fa[N];
int son[N];
bool vis[N];
vector<int> G[N];
mt19937 rnd(time(0));
void dfs(int u)
{
if(G[u].empty())
return;
random_shuffle(G[u].begin(),G[u].end());
int tmp=G[u][rnd()%G[u].size()];
for(int v:G[u]){
if(dep[v]!=dep[u]+1)
continue;
int dis;
if(v==tmp)
dis=0;
else{
cout<<"? 1 "<<tmp<<" "<<v<<endl;
cin>>dis;
}
if(dis==dep[tmp]-dep[v]){
son[u]=v;
break;
}
}
for(int v:G[u])
vis[v]=0;
for(int v:G[u]){
if(v==son[u]||dep[v]!=dep[u]+1)
continue;
cout<<"? 2 "<<v<<endl;
int siz,x;
cin>>siz;
for(int i=1;i<=siz;i++){
cin>>x;
if(v!=x)
G[v].push_back(x),vis[x]=1;
}
}
for(int v:G[u]){
if(dep[v]!=dep[u]+1&&!vis[v])
G[son[u]].push_back(v);
}
for(int v:G[u]){
if(dep[v]==dep[u]+1){
fa[v]=u;
dfs(v);
}
}
}
signed main()
{
cin>>n;
for(int i=2;i<=n;i++){
cout<<"? 1 1 "<<i<<endl;
cin>>dep[i];
G[1].push_back(i);
}
dfs(1);
cout<<"!";
for(int i=2;i<=n;i++)
cout<<" "<<fa[i];
cout<<endl;
return 0;
}
|