P11991 [JOIST 2025] 多方通信 / Multi Communication做题笔记
题意
有 \(n\) 个人,每一个人有一块黑板,有一个人的黑板上写着 \(1\),其他人的黑板上写着 \(0\)。要求进行 \(L\) 轮操作,每次操作每个人都把自己黑板上的数擦去再写上一个数,然后每个人都选择另外一个人,并查看它黑板上的数字。要求操作结束后所有人都知道谁的黑板上一开始写的 \(1\)。请你为他们提供策略(每个人根据自己的编号,初始时黑板的数字以及每次看到的数字决定在黑板上写什么,下次看谁)。
你能得到满分如果你能解决 \(n=48, L=9\) 的情况。
思路
显然是 ds 题,思路肯定是先预处理,让每个人都负责查看一批人中是否有目标人物。然后进行计算,通过直接查看负责人的方式,一次性排除一批非目标人物。考虑分块,发现能做到 \(L=24\)。
那么考虑二叉树。如果使用 Nodey Tree,那么查找会带上 \(2\) 倍常数,因为如果你判断目标点不在左儿子内,那么还有可能在父节点上或右儿子内,需要多一次判断。那么尝试 Leafy Tree,那如果用线段树的话预处理会带上两倍常数,因为父节点需要查询两个儿子的信息。
此时一个比较好的解法时使用树状数组,事实上,我们不需要知道右子树的信息,因为左子树没有已经意味着右子树有。这样,我们只维护二叉树上所有左子树的信息,那么预处理和查询都是单倍常数。
这样应该可以做到 \(n=48, L=10\),可惜我也不知道如何优化掉最后的一点点。
解法
考虑树状数组在预处理中有哪些浪费的情况,实际上全过程右儿子都没有查询信息,都浪费掉了。那么尝试不使用树形结构。除了分块和树形数据结构外,还有一个神奇的数据结构:倍增。如果把所有人围成一个环,那么容易通过 \(4\) 次倍增,确定每个人出发,右侧 \(16\) 个人中是否包含目标人物。
现在还有 \(5\) 次操作,我们可以从 \(32\) 个人中二分得出目标人物,但总共有 \(48\) 个人,怎么办?事实上,倍增过程中已经了解了自己出发右侧 \(16\) 个人中是否包含目标人物,那么如果没有,就可以把这 \(16\) 个人排除掉。
所以说实际上看起来最有希望的一条路走不通,就尝试一下除了这条路,有没有别的思路。不能因为这条路看起来最有希望就不去考虑别的路。
代码
typedef long long ll;
#define rep(i, a, b) for(ll i=a;i<=b;i++)
#define rrep(i, a, b) for(ll i=a;i>=b;i--)
void solve48(){
cout << 9 << endl;
rep(tar, 0, 47){//i是目标人物
cout << tar+1 << endl;
rep(i, 0, 47){
char lac;
rep(j, 0, 3){
if((tar-i+48)%48<(1LL<<j))cout << "T ", lac='T';
else cout << "F ", lac='F';
cout << (i+(1LL<<j))%48+1 << ' ';
}
if((tar-i+48)%48<(1LL<<4))lac='T';
else lac='F';
if((tar-i+48)%48<=15){//在自己的范围内
ll l=i, len=16;
while(len>1){
cout << lac << ' ' << (l+len/2)%48+1 << ' ';
len/=2;
if((tar-l+48)%48>=len)(l+=len)%=48;
}
cout << lac << ' ' << 1 << endl;
}else{
ll l=(i+16)%48, len=32;
while(len>1){
cout << lac << ' ' << (l+len/2)%48+1 << ' ';
len/=2;
if((tar-l+48)%48>=len)(l+=len)%=48;
}
cout << endl;
}
}
}
}

浙公网安备 33010602011771号