1 条题解

  • 0
    @ 2026-4-3 2:23:49

    D1. 杀手(简单版本)详细题解

    1. 问题理解

    • nn 个玩家,角色分为:

      • 骑士:总是说真话
      • 恶棍:总是说谎
      • 内鬼:恰好一个,行为与恶棍相同(总是说谎),但其他玩家认为他是骑士
    • 询问:? i j → 玩家 ii 认为玩家 jj 是骑士吗?返回 1(是)或 0(否)

    • 目标:在最多 n+69n + 69 次询问内找出内鬼


    2. 关键观察

    观察 1:角色回答表

    ii 的角色 jj 的角色 回答
    骑士 骑士 1
    恶棍 0
    内鬼 1
    恶棍 骑士 0
    恶棍 1
    内鬼 0
    内鬼 骑士
    恶棍 1

    注意:内鬼和恶棍的行为完全一样(都说谎),区别仅在于别人如何看待他们。

    观察 2:双向询问的性质

    如果询问 ? i j? j i

    • 如果 iijj 都不是内鬼
      • 两人要么都是骑士,要么都是恶棍,要么一骑士一恶棍
      • 可以验证:两个回答相等(都是 1 或都是 0)
    • 如果其中有一个是内鬼
      • 两个回答不相等(一个 1,一个 0)

    证明:

    • 两人都不是内鬼:根据角色组合,两种情况:

      • 同类型(骑士-骑士 或 恶棍-恶棍):两个回答都是 1
      • 不同类型(骑士-恶棍):骑士说 0,恶棍说 0?等等,骑士说 0(因为恶棍不是骑士),恶棍说 1(因为恶棍说谎,实际上对方是骑士,恶棍说不是 → 说谎 → 回答 1) → 不相等! 实际上需要仔细验证:

      检查所有非内鬼组合:

      1. 骑士-骑士:i 说 1,j 说 1 → 相等
      2. 恶棍-恶棍:i 说 1(说谎),j 说 1 → 相等
      3. 骑士-恶棍:i 说 0,j 说 1(恶棍说谎,对方是骑士,所以说不是 → 回答 1) → 不相等!

      所以上面的说法不正确。需要重新分析。

    修正观察:实际上,从表中可以看出:

    • 骑士-骑士:1,1 → 相等
    • 骑士-恶棍:0,1 → 不相等
    • 恶棍-骑士:0,1 → 不相等
    • 恶棍-恶棍:1,1 → 相等

    所以双向询问相等当且仅当两人角色相同(都是骑士或都是恶棍),而不是“都不是内鬼”。但内鬼混在其中时情况更复杂。

    不过标程用的观察是:如果 query(i,j) != query(j,i),则其中一个是内鬼。这个结论需要验证。

    实际上,包含内鬼的情况:

    • 骑士-内鬼:1,0 → 不相等
    • 恶棍-内鬼:1,0 → 不相等
    • 内鬼-骑士:0,1 → 不相等
    • 内鬼-恶棍:0,1 → 不相等

    所以确实,只要其中一个是内鬼,双向询问结果就不相等

    因此,双向询问结果相等 ⇔ 两人都不是内鬼且角色相同
    但更重要的是:结果不相等 ⇒ 其中一个是内鬼


    3. 核心算法思路

    利用上述性质,我们可以成对地排除非内鬼的玩家:

    1. 每次取最后两个玩家 n1n-1nn,询问 ? n-1 n? n n-1
    2. 如果结果不同,说明其中一个是内鬼 → 进入候选处理。
    3. 如果结果相同,说明两人都不是内鬼(且角色相同),可以安全地忽略他们,nn 减少 22
    4. 重复直到 n4n \le 4
    5. 对于 n=3n = 3n=4n = 4,用预处理的 44 次询问找出内鬼。

    4. 算法步骤

    4.1 缩小规模(n>4n > 4

    while n > 4:
        a = query(n-1, n)
        b = query(n, n-1)
        if a != b:
            candidates = {n-1, n}
            break
        else:
            n -= 2
    

    4.2 处理候选对

    如果找到了候选对 {x, y}(其中一个是内鬼),我们需要确定哪个是内鬼。

    选择一个已知不是内鬼的玩家 z

    • 如果 n 减少了,我们可以用之前的某个玩家(如 n-2N 以外的玩家)
    • 实际上,由于我们在 n > 4 时成对移除,剩下的玩家中至少有一个不是内鬼(因为内鬼只会在候选对中)

    询问 ? x z? z x

    • 如果结果不同,则 x 是内鬼
    • 否则 y 是内鬼

    4.3 处理 n=3n = 3 的情况

    对于 3 个玩家,可以用 3 次询问找出内鬼(标程用了 4 次,但可以优化):

    1. 询问 ? 1 2? 2 1
      • 如果不同,则内鬼在 {1,2} 中,再用一次询问 ? 1 3? 3 1 确定具体是谁
      • 如果相同,则 1 和 2 都不是内鬼,内鬼是 3

    4.4 处理 n=4n = 4 的情况

    标程中用了 4 次询问:

    1. 询问 ? 1 2? 2 1
      • 如果不同,内鬼在 {1,2} 中,再用 ? 1 3? 3 1 判断
      • 如果相同,则 1 和 2 都不是内鬼,再询问 ? 1 3? 3 1
        • 如果不同,内鬼是 3
        • 如果相同,内鬼是 4

    5. 询问次数分析

    • 每对玩家(n>4n > 4)使用 22 次询问
    • 最多 n/2\lfloor n/2 \rfloor 对,但一旦找到候选对就停止
    • 最坏情况:所有对都相同,直到 n4n \le 4,使用约 n4n-4 次询问
    • 最后 n=3n=3n=4n=4 需要最多 44 次询问
    • 总询问次数 n+4\le n + 4,远小于 n+69n + 69

    6. 代码实现要点

    bool query(int i, int j) {
        cout << "? " << i << " " << j << endl;
        int ans;
        cin >> ans;
        return ans;
    }
    

    注意:

    • 每次询问后刷新输出
    • 处理 n 的副本,因为原始 N 用于最后输出

    7. 正确性证明

    • 性质保证:双向询问结果不同 ⇒ 其中一个是内鬼
    • n>4n > 4 且双向结果相同时,两人都不是内鬼,可以安全移除
    • 最终规模缩小到 3344,可以通过穷举找出内鬼
    • 由于内鬼唯一,算法总能正确识别

    8. 时间复杂度

    • 每次询问 O(1)O(1)
    • 总询问次数 O(n)O(n)
    • 适应 n105n \le 10^5n105\sum n \le 10^5 的约束

    9. 总结

    核心思想:

    1. 双向询问可以检测内鬼
    2. 成对排除非内鬼玩家,缩小问题规模
    3. 小规模直接枚举解决
    4. 总询问次数 n+4\le n + 4,满足 n+69n + 69 的限制
    • 1

    信息

    ID
    6318
    时间
    1000ms
    内存
    256MiB
    难度
    2
    标签
    递交数
    1
    已通过
    1
    上传者