0%

CF-1079-E2. Interactive Graph (Hard Version)

题目大意

题目名称:E2. Interactive Graph (Hard Version)(交互式图困难版)

题目描述
这是一个交互题。
后台有一个包含 nn 个顶点和 mm 条边的有向无环图(DAG),图中没有自环和重边。
你的任务是在最多进行 n+mn + m 询问的情况下,确定图中所有的边。

你可以进行如下格式的询问:
? k

系统会返回该图中按字典序排列的所有路径列表中的第 kk 条路径。

  • 路径定义:一个顶点序列 u1,u2,,ulu_1, u_2, \dots, u_l,满足图中存在边 (ui,ui+1)(u_i, u_{i+1})

  • 字典序定义:序列 aa 小于序列 bb 当且仅当 aabb 的真前缀,或者在两者第一个不同的位置上,aa 的元素小于 bb 的元素。

  • 返回格式:系统首先返回路径长度 qq。如果 q=0q=0,表示不存在第 kk 条路径;否则,随后返回 qq 个整数表示该路径的顶点序列。

  • 数据范围1n301 \le n \le 301k2301 \le k \le 2^{30}

输出格式
当你确定了所有边后,输出 ! m,随后输出 mm 行,每行两个整数 u,vu, v,代表存在一条从 uuvv 的有向边。

样例

Input

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
3
5

1 1

2 1 2

3 1 2 4

3 1 2 5

2 1 3

3 1 3 4

3 1 3 5

1 2

1 3

1 4

1 5

1

0

2

1 1

1 2

2 2 1

Output

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


? 1

? 2

? 3

? 4

? 5

? 6

? 7

? 8

? 11

? 14

? 15

! 6
1 3
1 2
2 4
3 4
2 5
3 5

? 2

! 0

? 1

? 2

? 3

! 1
2 1

样例解释
以第一个测试用例为例:

  • n=5n=5

  • 程序依次询问了第 1, 2, …, 15 条路径。

  • 例如,? 3 返回了 3 1 2 4,表示字典序第 3 小的路径是 1241 \to 2 \to 4

  • 通过这些路径信息,程序最终判断出图中有 6 条边,并输出了它们(如 131 \to 3, 121 \to 2 等)。

思路讲解

image

那么我们把我们可以记忆化的东西已经全部在图上标出来了。我们为什么要记忆化呢?其实我们的目标就在于,我们每次询问啊,我们都尽量想比较好的去找到它的出边,是吧?

如果有大量的这个路径,它实际上是没有什么信息的增量的。它大量的这个路径啊,就是它没有什么信息的增量。

比如说我们来看12这个路径,它就告诉了我们从12有一条出边。然后我们现在想要看1还有哪些出边,那么这个时候124、125实际上没有给我们带来信息的增量,但是13啊又给我们带来了信息的增量,但是134,135。但实际上又没有了信息的增量。所以我们就要想办法去跳过没有信息增量的东西。比如说12,它没有信息的增量。就是1~2以后,然后我们查找到 cnt 2实际上有3个,然后我们直接便利到12的时候,我们就直接跳到13,是不是就会好很多?

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
auto solve=[&](auto && self,ll u,ll cur_k) ->  void {
// 以前已经遍历到过了,这里就直接 return 就好了。
// 我们都是把返回值放在数组里面的,所以我们的返回类型是 void。
if (cnt_ls[u]) {
return;
}
vis[u]=1;
cnt_ls[u]=1;
while (true) {
vector<ll> res=query(cur_k+cnt_ls[u]);
auto it=find(all(res),u);
// 这个 it 不能够往后移的,这个迭代器不能够往后移的
// 那就说明没有 to 这个点了,我们就直接退出即可。
if (it==res.end() || it==prev(res.end())) {
return;
}
ll to=*next(it);
self(self,to,cur_k+cnt_ls[u]);
edges.push_back({u,to});
cnt_ls[u]+=cnt_ls[to];
}
};
ll cur_k=0;
for (ll u=1;u<=N;++u){
if (!vis[u]) {
solve(solve,u,cur_k+1);
}
// 注意,即便是已经被 vis 的点,就即便是已经遍历过的点,我们仍然需要去跳过这些点。
// 跳过的方式就是给目前的这个 K 加上这个 cnt_ls[u]。
cur_k+=cnt_ls[u];
}

AC代码

AC
https://codeforces.com/contest/2197/submission/362863587

心路历程(WA,TLE,MLE……)