1 条题解

  • 0
    @ 2025-12-11 21:55:32

    1. 算法分析

    核心结论: 先手(TBL)必胜当且仅当存在一个非空子集,其异或和为 0(即原数列线性相关)。 反之,如果所有非空子集的异或和都不为 0(即原数列线性无关),则先手必败。

    题目要求与输出的对应关系:

    • 根据题目描述:“如果胜则输出 NO,否则输出 YES”。
    • 代码逻辑:如果找到一个子集异或和为 0,输出 NO(代表 TBL 胜);如果遍历完所有子集都没找到,输出 YES(代表 TBL 败)。

    2. 博弈论推导

    为什么“存在子集异或和为 0”意味着先手必胜?

    我们可以将巧克力棒分为两部分:留在盒子里的(Reserve)和拿出来的(Table)。 Nim 游戏的胜负取决于桌面上物品的异或和。

    必胜策略如下:

    1. 如果存在一个子集 SS,其元素异或和为 0(xSx=0\bigoplus_{x \in S} x = 0)。
    2. 先手(TBL)的第一步操作是:将除了 SS 以外的所有巧克力棒都拿出来
    3. 此时,留在盒子里的巧克力棒集合正是 SS,且它们的异或和为 0。
    4. 无论后手(X)如何操作(从桌上吃或从盒子里拿),先手总有办法维持某种平衡,迫使后手最终面对“盒子空了且桌上异或和为 0”的必败局面。
      • 简单理解:留在盒子里的 SS 相当于一个“零元素”。无论后手从盒子里拿出什么(设为 ss'),ss' 的异或和一定不为 0(因为线性无关的性质或最小依赖集性质)。这打破了平衡,先手可以通过操作桌面的局面或者继续利用盒子里的剩余部分来重新控制局面。

    反之(线性无关): 如果任何子集的异或和都不为 0,无论先手第一步拿什么出来,留下的和拿出来的都不可能构成异或和为 0 的局面。后手可以通过特定策略一直维持优势。

    3. 代码详解

    这份代码使用了 二进制枚举(状态压缩) 来检查是否存在异或和为 0 的子集。

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 20;
    int a[N], n, m, T = 10; // T是轮数,题目固定为10轮
    
    void work() {
        // 读入棒子数量
        cin >> n;
        // m = 2^n,即状态总数
        m = 1 << n;
    
        // 读入每根棒子的长度
        for (int i = 0; i < n; i++)
            cin >> a[i];
    
        // 遍历所有非空子集
        // i 从 1 枚举到 2^n - 1
        // r 用于存储当前子集的异或和
        for (int i = 1, r; r = 0, i < m; i++) {
            // 遍历每一位,检查第 j 根棒子是否在当前子集 i 中
            for (int j = 0; j < n; j++)
                if (i & (1 << j))
                    r ^= a[j]; // 如果在,则进行异或运算
    
            // 如果发现某个子集的异或和为 0
            if (!r)
                // 根据题意:胜输出 NO。return void 结束当前这局的判断
                return (void)(puts("NO"));
        }
    
        // 如果所有子集遍历完都没有发现异或和为 0 的情况
        // 说明先手必败,输出 YES
        puts("YES");
    }
    
    int main() {
        // 处理 T 组数据
        while (T--)
            work();
    }
    

    4. 复杂度分析

    • 时间复杂度O(TN2N)O(T \cdot N \cdot 2^N)
      • T=10T = 10
      • N14N \le 14
      • 214=163842^{14} = 16384
      • 总运算量约为 10×14×163842.3×10610 \times 14 \times 16384 \approx 2.3 \times 10^6,远小于 1秒(通常可处理 10810^8 级别)的时间限制。
    • 空间复杂度O(N)O(N),仅需存储数组。

    5. 替代解法(线性基)

    虽然本题 NN 很小,直接暴力枚举 2N2^N 即可通过。但如果 NN 较大(例如 N=100N=100),则需要使用 线性基(Linear Basis)。 线性基可以在 O(Nlog(maxL))O(N \cdot \log(\max L)) 的时间内判断一组数是否存在子集异或和为 0。

    线性基逻辑: 尝试将每个数字插入线性基。如果某个数字无法插入(即它能被线性基中已有的数字异或表示出来),说明存在线性相关,即存在子集异或和为 0。

    // 线性基解法片段(适用于 N 较大的情况)
    bool can_win = false;
    memset(p, 0, sizeof(p)); // p为线性基数组
    for(int i=0; i<n; i++) {
        if(!insert(a[i])) { // insert返回false表示插入失败,即a[i]可被表示
            can_win = true; 
            break; 
        }
    }
    if(can_win) puts("NO");
    else puts("YES");
    

    对于本题 N14N \le 14,题目给出的暴力枚举代码是最直观且完全够用的。

    • 1

    信息

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