CF1526F Median Queries 题解

Description

本题是一道交互题。

给定 nn,你需要猜测一个长度为 nn 的排列 pp(即 pp 包含所有 11nn 的整数各一次)。已知 pp 满足 p1<p2p_1<p_2

当然,你可以进行询问,每次询问你需要给定三个互不相同的整数 a,b,ca,b,c,交互器会返回 papb,pbpc,pcpa|p_a-p_b|,|p_b-p_c|,|p_c-p_a| 这三个数中第二小的数。

请在 2×n+4202\times n+420 次询问内求出整个排列 ppTT 组数据。

交互格式如下:

  • 首先你需要读入整数 TT 表示数据组数。接下来对于每组数据:
  • 首先读入一个整数 nn 表示排列长度。如果你希望询问,按照 ? a b c 的格式输出,你将收到 papb,pbpc,pcpa|p_a-p_b|,|p_b-p_c|,|p_c-p_a| 中第二小的数。你需要保证 1a,b,cn1\leq a,b,c\leq n,且 a,b,ca,b,c 互不相同。请注意,如果你的询问不合法,或者你的询问次数过多,交互器会给出 -1 的结果,此时你需要立即停止程序,否则可能会得到任意的评测结果。如果你希望回答,按照 ! p_1 p_2 ... p_n 的格式输出。如果你的回答正确,交互器会给出 1 的结果,此时你需要直接进行下一组数据(如果有的话),否则交互器会给出 -1 的结果,同样的你需要立刻结束程序。

注意刷新缓冲区。

1T103,20n,n1051\leq T\leq10^3,20\leq n,\sum n\leq10^5

Solution

考虑怎么一一确定每个数。

注意到如果已经直到 1122n1n-1nn 的位置之后,再通过 n2n-2 次操作就能确定每个数了。

同时我们如果只知道边上的两个数,但不知道是 1,21,2 还是 n1,nn-1,n,也是可以做的。因为可以先把边上的数当作 1,21,2 确定出整个排列,如果 p1>p2p_1>p_2 则让 pinpi+1p_i\leftarrow n-p_i+1,否则不变。

现在问题转化为怎么找到边上的两个数。

有个不显然的观察是对于一对距离不长的 (a,b)(a,b),与它们距离最大的两个点一定在边界上。结论是 ba+1n43b-a+1\leq\frac{n-4}{3} 时就是对的。

找到长度不超过 n43\frac{n-4}{3} 的点对是简单的,可以一直随,直到随到权值 n46\leq\frac{n-4}{6}(a,b,c)(a,b,c) 即可。单次随到的概率是 19\frac{1}{9}420420 次一定能随到。

细节见代码。

Code

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
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 1e5 + 5;

int n;
int p[kMaxN], val[kMaxN];
std::mt19937 rnd(std::random_device{}());

int ask(int x, int y, int z) {
std::cout << "? " << x << ' ' << y << ' ' << z << std::endl;
int d;
std::cin >> d;
return d;
}

std::tuple<int, int, int> gettuple() {
int a = rnd() % n + 1, b = rnd() % n + 1, c = rnd() % n + 1;
while (a == b || a == c || b == c) a = rnd() % n + 1, b = rnd() % n + 1, c = rnd() % n + 1;
return {a, b, c};
}

std::pair<int, int> getpair() {
int a, b, c;
for (int c = 1; c <= 420; ++c) {
std::tie(a, b, c) = gettuple();
if (ask(a, b, c) <= (n - 4) / 6) return {a, b};
}
}

void dickdreamer() {
std::cin >> n;
auto [a, b] = getpair();
for (int i = 1; i <= n; ++i) {
val[i] = 0;
if (i != a && i != b) val[i] = ask(a, b, i);
}
int p1 = 0, p2 = 0;
p1 = std::max_element(val + 1, val + 1 + n) - val;
std::vector<int> vec;
for (int i = 1; i <= n; ++i) {
if (val[i] == val[p1] - 1)
vec.emplace_back(i);
}
if ((int)vec.size() == 1) {
p2 = vec[0];
} else {
assert(vec.size() == 2);
if (ask(a, p1, vec[0]) < ask(a, p1, vec[1])) p2 = vec[0];
else p2 = vec[1];
}
p[p1] = 1, p[p2] = 2;
for (int i = 1; i <= n; ++i)
if (i != p1 && i != p2)
p[i] = ask(i, p1, p2) + 2;
if (p[1] > p[2]) {
for (int i = 1; i <= n; ++i)
p[i] = n - p[i] + 1;
}
std::cout << "! ";
for (int i = 1; i <= n; ++i) std::cout << p[i] << ' ';
std::cout << std::endl;
std::cin >> n;
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
// std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

CF1526F Median Queries 题解
https://sobaliuziao.github.io/2024/09/20/post/ca92bb9b.html
作者
Egg_laying_master
发布于
2024年9月20日
许可协议