「EZEC-8」猜树 加强版 题解

Page Views Count

「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;
}