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;
            }
        }
    }
}
posted @ 2025-07-10 13:23  yanzihe  阅读(15)  评论(0)    收藏  举报