LOJ #3831. 「IOI2022」囚徒挑战 题解

Description

一个监狱里关着 500500 名囚徒。 有一天,监狱长给了他们一个重获自由的机会。 他把装钱的两个袋子 A 和 B 放在一个房间里。 每个袋子装有若干枚硬币,数量的范围在 11NN 之间(包含 11NN)。 两个袋子里硬币的数量不同。 监狱长给囚徒们提出了挑战,目标是指出硬币数量较少的那个袋子。

房间里除了袋子还有一块白板。 任意时刻白板上写着一个数,一开始写的是 0。

监狱长让囚徒一个接一个地进入房间。 每个进入房间的囚徒不知道他之前进入过房间的囚徒有多少人,也不知道是哪些人。 每次一个囚徒进入房间时,他看一眼白板上目前写的这个数。 看完之后,他必须在袋子 A 和 B 之间做出选择。 接着,他检查自己选的那个袋子,知道了里面有多少枚硬币。 然后,这名囚徒必须选择做以下两种行动之一:

  • 将白板上的数改写成一个非负整数,并离开房间。 注意他可以改变成新的数,也可以保留当前的数。 然后挑战继续进行(除非所有 500500 名囚徒都已经进过房间)。
  • 指出硬币数量较少的那个袋子。这会立即结束挑战。

对于已经进过房间的囚徒,监狱长不会让他再次进入房间。

如果某个囚徒正确地指出硬币较少的袋子,则囚徒们获得挑战的胜利。 如果指出的袋子不正确,或者所有 500500 人进过房间之后还没有人尝试指出硬币较少的袋子,则囚徒们失败。

挑战开始之前,囚徒们集合在监狱大厅商量应对挑战的共同策略,分以下三个步骤:

  • 他们挑选一个非负整数 xx,作为他们可能会写在白板上的最大的数。
  • 他们决定对任意一个数 i(0ix)i (0 \le i \le x),如果某个囚徒进入房间后看到白板上写着数 ii,那么他应该去检查哪个袋子。
  • 他们决定当某个囚徒得知选中的袋子里的硬币数量后要采取的行动。具体来说,对任意写在白板上的数 i(0ix)i (0 \le i \le x) 和检查选中的袋子里的硬币数量 j(1jN)j (1 \le j \le N),他们要决定做出以下两种行动之一:
    • 白板上应该要写一个 00xx 之间(包含 00xx)的什么数;
    • 指出哪个袋子是硬币较少的。

如果赢得挑战,监狱长会在囚徒们继续服刑 xx 天后释放他们。

N5000,x=20N\leq 5000,x=20

Solution

首先这题有个很简单的想法是按照 22 进制从高位到低位进行比较,并且把上一位的值压在状态里。即从高位到低位的第 ii 位,如果当前数是 00,则为 2i+12i+1,否则为 2i+22i+2。然后对于这两种状态去询问与它们之前询问的数不同的那个,即可得到另一个数的值,如果能在第 ii 位比较出来,则直接结束。否则递归到第 i+1i+1 位,同时每次询问的数交替即可。

这么做是 2log2N=262\left\lceil\log_2N\right\rceil=26 次,不太能过。改成 33 进制可以变成 2424 次。

考虑优化。由于最后一定能比出大小,所以递归到最低位时,如果当前位为 00 或者 22,则可以直接返回。于是就变成 2222 次了。

上面的做法已经不好优化了,考虑将 33 进制改成类似线段树的结构,即我们可以确定当前数在 [l,r][l,r] 的范围内,然后询问另一个数,如果能比出大小就结束,否则将 [l,r][l,r] 平均分成 33 份进行处理,显然这是和原先的做法等价的。

这个做法和上面的小优化容易结合起来,即如果询问出来的数为 ll 或者 rr 就能结束,也就是区间长度 kk 变为 k23\left\lceil\frac{k-2}{3}\right\rceil,经过计算次数变为 2121 次。

最后的优化就是注意到区间长度不超过 66 时进行三分是很浪费的,因为划分后长度为 11 还是 22 是一样的,所以这个时候二分是一样的,总次数就变为 2020 次了。

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

const int kV[] = {3, 3, 3, 3, 3, 3, 2}, kS[] = {0, 3, 6, 9, 12, 15, 18, 20};

int N;
std::vector<std::vector<int>> res;

void solve(int d, int x, int l, int r, int L, int R) {
if (l > r) return;
int op = (d & 1);
res[x][0] = op;
int v = (d <= 6 ? kV[d] : 1), len = r - l - 1, k[v] = {0};
for (int i = 0; i < v; ++i) k[i] = (len + v - 1 - i) / v;
for (int i = L; i <= R; ++i) {
if (i <= l) {
res[x][i] = -op - 1;
} else if (i >= r) {
res[x][i] = op - 2;
} else {
res[x][i] = kS[d];
for (int j = 0, s = l + 1; j < v; s += k[j++]) res[x][i] += (i >= s);
}
}
for (int i = 0, s = l + 1; i < v; s += k[i++]) solve(d + 1, kS[d] + 1 + i, s, s + k[i] - 1, l, r);
}

std::vector<std::vector<int>> devise_strategy(int N) {
res.resize(21, std::vector<int>(N + 1, 0));
::N = N, solve(0, 0, 1, N, 1, N);
return res;
}

LOJ #3831. 「IOI2022」囚徒挑战 题解
https://sobaliuziao.github.io/2025/08/31/post/c73e7a6a.html
作者
Egg_laying_master
发布于
2025年8月31日
许可协议