题解:P8491 [IOI 2022] 囚徒挑战
由于所有人都不知道自己是谁,他的决策只能依据黑板上的数字,因此有 \(500\) 个人这个条件实际上是没用的,真正有用的实际只有 \(x\) 个人。
省略一些写的数字大小是 \(O(n)\) 的做法。
本题解中,一个数在某个进制下的第 \(k\) 位,指从低位往高位数的第 \(k\) 位。
\(\boldsymbol {x = 52}\)
由于每个人可以获得的信息非常少,我们考虑按位比较,找到第一个不同的位,然后报告答案。
我们可以在黑板上记录这三样信息:
- \(0/1\):前一个人看的是 A/B。
- \(0 \sim 12\) 的一个数字:前一个人看的是第几位。
- \(0/1\):这一位上的数字是 \(0/1\)。
一个人进入房间后,先观察上一个人看的是 A 还是 B。如果是 A 就继续翻看 B 的同一位,并进行比较;如果是 B 那么就翻看 A 的下一位。
那么状态数就是 \(52\)。
\(\boldsymbol{x = 39}\)
发现上面有状态浪费。\((1,x, 0/1)\) 这样的一个状态是没用的,也就是说,我们读取 A 的当前位时,并不关心 B 的上一位是多少。
我们可以在黑板上记录这两样信息:
- \(0 \sim 12\):当前看的是第几位。
- \(0/1/2\):如果是 \(0/1\) 表示 A 的这一位是什么;如果是 \(2\) 表示还没查看 A 的这一位。
在此基础上,我们套用 \(x = 52\) 的构造策略,即可做到 \(x = 39\)。
\(\boldsymbol{x = 26}\)
发现上面的那个构造是将 A,B 放在不平等的位置,而 \((x, 2)\) 这样的一个状态看起来还是很浪费。
我们发现,上面那个做法,我们在查看 B 的时候,不仅可以看到 B 的当前位并和 A 的当前位比较,还能同时记录下 B 的下一位。然后下一个人在查看 A 之后,不仅可以将 A 的下一位和 B 的下一位比较,还能接着记录 A 的下两位……这样交替的状态,可以将形如 \((x, 2)\) 这类空状态省去。
这样我们只需要记录:
- \(0 \sim 12\):当前看的是第几位。
- \(0/1\):上一个人看的盒子中,这一位上的值。
由于是奇偶交替的,上一个人看的具体是哪个盒子,可以通过当前是第几位算出来。
由此做到 \(26\)。
\(\boldsymbol{x = 24}\)
我们试图不再使用二进制,假设用 \(B\) 进制,那么需要的状态数就是 \(B \lceil \log_B 5000 \rceil\)。经验证,\(B = 3\) 的时候比较优秀。
然后我们沿用 \(x = 26\) 的做法,即可做到 \(x = 24\)。
\(\boldsymbol{x = 22}\)
我们发现,到了最后一位,假如我们看到了 \(0/2\),那么我们就不需要检查另一个盒子了,因为题目保证了两个盒子的球数不相同。
那么我们可以省略掉 \((0, 0)\) 和 \((0, 2)\) 两个状态,可以做到 \(22\)。
\(\boldsymbol{x = 21 / 20}\)
这个时候要动大手术了,发现上面那个做法已经卡得很满了。但是 \(x = 22\) 的做法启发我们,在某些触碰到边界的地方,不需要新开一个状态记录,而是可以直接返回。
用进制表示有它的局限性。我们可以用一个类似线段树的结构来理解它。对于三进制的情形,那我们刚才的做法,就是将每个区间分成三块,如果目前 A,B 不在同一块,则可以比出大小;否则如果在同一块,就进入这个小块重复这个过程。
而回顾线段树干的事情:每个节点代表一个区间。这是否意味着,对于线段树上每个节点,如果我们进入这个节点的时候踩着左端点或者右端点,这个时候就可以直接比出大小,而不需要再进行递归计算了!
我们将三叉线段树调整成如下图的结构。

我们把每个区间的左端点和右端点抠掉(图中红色和蓝色的部分),这些位置是可以直接比出大小的,不需要再继续进行分段。
在去掉每个区间的端点之后再进行刚才那个交替的过程,可以做到 \(21\)。
优化到 \(20\) 也不难,完全不需要 dp。
经过肉眼观察,我们发现有一步要把一个长度为 \(6\) 的区间分三段。但是实际上分两段会更优,如图所示。

我们在最后一层换成一个二叉的结构,其余层沿用三叉的结构,可以做到 \(20\)。
那么这道题就做完了。在代码实现中,我先把这颗树建出来,处理出每个点在每一层属于那个区间。然后对每个节点分别考虑它会到达下一层的那个节点即可。
实现细节和题解代码都不太一样。
#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
constexpr int all=5102;
int tot,id[10][4],d[all+10],col[all+10],bel[10][all+10];
//-1: minimum; -2: maximum
void init(int l,int r,int dep)
{
bel[dep][l]=-1,bel[dep][r]=-2;
if(r-l<=1)return;
if(r-l==5)
{
if(!id[dep][0])
id[dep][0]=++tot,d[tot]=dep,col[tot]=0;
if(!id[dep][1])
id[dep][1]=++tot,d[tot]=dep,col[tot]=1;
int mid=l+r>>1;
for(int i=l+1;i<=mid;i++)bel[dep][i]=id[dep][0];
for(int i=mid+1;i<r;i++)bel[dep][i]=id[dep][1];
init(l+1,mid,dep+1);
init(mid+1,r-1,dep+1);
return;
}
int len=r-l-1,mid1=l+len/3,mid2=mid1+len/3;
if(!id[dep][0])
id[dep][0]=++tot,d[tot]=dep,col[tot]=0;
if(!id[dep][1])
id[dep][1]=++tot,d[tot]=dep,col[tot]=1;
if(!id[dep][2])
id[dep][2]=++tot,d[tot]=dep,col[tot]=2;
for(int i=l+1;i<=mid1;i++)bel[dep][i]=id[dep][0];
for(int i=mid1+1;i<=mid2;i++)bel[dep][i]=id[dep][1];
for(int i=mid2+1;i<r;i++)bel[dep][i]=id[dep][2];
init(l+1,mid1,dep+1);
init(mid1+1,mid2,dep+1);
init(mid2+1,r-1,dep+1);
}
vector<vector<int> > devise_strategy(int n)
{
init(1,all,0);
vector<vector<int> > s;
for(int i=0;i<=20;i++)
{
s.push_back(vector<int> (n+1,0));
if(i==0)
{
s[i][0]=0;
for(int j=1;j<=n;j++)s[i][j]=bel[0][j];
} else {
int now_d=d[i];
s[i][0]=(now_d&1)^1;
for(int j=1;j<=n;j++)
{
if(bel[now_d][j]==-1)s[i][j]=-1-s[i][0];
else if(bel[now_d][j]==-2)s[i][j]=-2+s[i][0];
else if(col[bel[now_d][j]]!=col[i]) {
if(col[bel[now_d][j]]<col[i])s[i][j]=-1-s[i][0];
else s[i][j]=-2+s[i][0];
} else {
int nxt_d=now_d+1;
if(bel[nxt_d][j]==-1)s[i][j]=-1-s[i][0];
else if(bel[nxt_d][j]==-2)s[i][j]=-2+s[i][0];
else s[i][j]=bel[nxt_d][j];
}
}
}
}
return s;
}

浙公网安备 33010602011771号