• 第六章 数学(三)


    四、容斥原理

    假设有三个圆如图重叠,使用 S i S_i Si表示三个圆的面积,那么整个图的面积为 S =   S 1 + S 2 + S 3 − S 1 ∩ S 2 − S 1 ∩ S 3 − S 2 ∩ S 3 + S 1 ∩ S 2 ∩ S 3

    S= S1+S2+S3S1S2S1S3S2S3+S1S2S3
    S= S1+S2+S3S1S2S1S3S2S3+S1S2S3在这里插入图片描述同理,对于四个圆相互重叠后,整个图形的面积为 S =   S 1 + S 2 + S 3 + S 4 − S 1 ∩ S 2 − S 1 ∩ S 3 − S 1 ∩ S 4 − S 2 ∩ S 3 − S 2 ∩ S 4 − S 3 ∩ S 4 + S 1 ∩ S 2 ∩ S 3 + S 1 ∩ S 3 ∩ S 4 + S 2 ∩ S 3 ∩ S 4 − S 1 ∩ S 2 ∩ S 3 ∩ S 4
    S= S1+S2+S3+S4S1S2S1S3S1S4S2S3S2S4S3S4+S1S2S3+S1S3S4+S2S3S4S1S2S3S4
    S= S1+S2+S3+S4S1S2S1S3S1S4S2S3S2S4S3S4+S1S2S3+S1S3S4+S2S3S4S1S2S3S4

    讨论:式子中元素个数

    假设有 n n n个数,那么总共有 C n 0 + C n 1 + C n 2 + C n 3 + . . . + C n n = 2 n \mathrm{C}_n^0+ \mathrm{C}_n^1+ \mathrm{C}_n^2+\mathrm{C}_n^3+...+\mathrm{C}_n^n = 2^n Cn0+Cn1+Cn2+Cn3+...+Cnn=2n其中等式左边表示 n n n个数中选择任意多个数的方案数,等式右边等于 2 n 2^n 2n是因为对于每个数而言只有选或者不选择两种情况, n n n个数总共有 2 n 2^n 2n。所以对于式子中元素个数为 C n 1 + C n 2 + C n 3 + . . . + C n n = 2 n − C n 0 = 2 n − 1 \mathrm{C}_n^1+ \mathrm{C}_n^2+\mathrm{C}_n^3+...+\mathrm{C}_n^n = 2^n - \mathrm{C}_n^0 = 2^n - 1 Cn1+Cn2+Cn3+...+Cnn=2nCn0=2n1所以容斥原理时间复杂度为 2 n 2^n 2n

    讨论:式子中为什么是“+-+-”不断重叠的

    假设有式子 ∣ S i ∪ S 2 ∪ . . . ∪ S n ∣ = ∑ i ∣ S i ∣ − ∑ i , j ∣ S i ∩ S j ∣ + ∑ i , j , k ∣ S i ∩ S j ∩ S k ∣ − . . . |S_i \cup S_2 \cup ... \cup S_n| = \sum_i|S_i| - \sum_{i,j}|S_i \cap S_j| + \sum_{i,j,k}|S_i \cap S_j \cap S_k| - ... SiS2...Sn=iSii,jSiSj+i,j,kSiSjSk...其中, ∣ S ∣ |S| S操作表示集合中 S S S的个数。

    假设有元素 x x x出现在了 k ∈ [ 1 , n ] k \in [1, n] k[1,n]个集合中,根据上述等式右边,可以得出元素 x x x总共被计算了 C k 1 − C k 2 + C k 3 − C k 4 + . . + ( − 1 ) k − 1 C k k = 1 \mathrm{C}_k^1 - \mathrm{C}_k^2 + \mathrm{C}_k^3 - \mathrm{C}_k^4 + .. + (-1)^{k-1}\mathrm{C}_k^k = 1 Ck1Ck2+Ck3Ck4+..+(1)k1Ckk=1所以可以知道对于等式左边中的任意一个元素 x x x,在统计的时候只会被统计一次,所以容斥原理的等式是正确的。

    容斥原理公式的代码实现原理:二进制枚举
    S =   S 1 + S 2 + S 3 + S 4 − S 1 ∩ S 2 − S 1 ∩ S 3 − S 1 ∩ S 4 − S 2 ∩ S 3 − S 2 ∩ S 4 − S 3 ∩ S 4 + S 1 ∩ S 2 ∩ S 3 + S 1 ∩ S 3 ∩ S 4 + S 2 ∩ S 3 ∩ S 4 − S 1 ∩ S 2 ∩ S 3 ∩ S 4

    S= S1+S2+S3+S4S1S2S1S3S1S4S2S3S2S4S3S4+S1S2S3+S1S3S4+S2S3S4S1S2S3S4
    S= S1+S2+S3+S4S1S2S1S3S1S4S2S3S2S4S3S4+S1S2S3+S1S3S4+S2S3S4S1S2S3S4总共有 2 n − 1 2^n-1 2n1项,每一项前面的符号取决于所选集合的个数。如果集合个数为奇数,则为 + + +,否则为 − -

    对于每个集合的枚举,使用位运算依次枚举 1... 2 n − 1 1...2^n-1 1...2n1,即有 n n n位二进制数,表示 n n n个集合。如果某位上值为 1 1 1表示选择该集合, 0 0 0表示不选择该集合。

    4.1 能被整除的数

    ACwing 890

    如果采用暴力枚举每个数,那么时间复杂度是 O ( n m ) O(nm) O(nm),显然会超时,所以使用容斥原理。对于样例,建立两个数组 S 2 = [ 2 , 3 , 6 , 8 , 10 ] S 3 = [ 3 , 6 , 9 ]

    S2=[2,3,6,8,10]S3=[3,6,9]
    S2=[2,3,6,8,10]S3=[3,6,9]分别表示能被 2 、 3 2、3 23整除的数,题目要求 ∣ S 2 ∪ S 3 ∣ |S_2 \cup S_3| S2S3,根据容斥原理有: ∣ S 2 ∪ S 3 ∣ =   ∣ S 2 ∣ + ∣ S 3 ∣ − ∣ S 2 ∩ S 3 ∣ =   5 + 3 − 1 =   7
    |S2S3|= |S2|+|S3||S2S3|= 5+31= 7
    = = = S2S3S2+S3S2S35+317
    时间复杂度为 O ( 2 m ) O(2^m) O(2m)

    其中对于 ∣ S p ∣ |S_p| Sp,即 1... n 1...n 1...n p p p的倍数的个数,有 ∣ S p ∣ = ⌊ n p ⌋ |S_p| = \left \lfloor \frac{n}{p} \right \rfloor Sp=pn对于 ∣ S p 1 ∩ S p 2 ∩ S p 3 ∩ . . . ∩ S p k ∣ |S_{p_1} \cap S_{p_2} \cap S_{p_3} \cap ... \cap S_{p_k}| Sp1Sp2Sp3...Spk,有 ∣ S p 1 ∩ S p 2 ∩ S p 3 ∩ . . . ∩ S p k ∣ = ⌊ n p 1 p 2 . . . P k ⌋ |S_{p_1} \cap S_{p_2} \cap S_{p_3} \cap ... \cap S_{p_k}| = \left \lfloor \frac{n}{p_1 p_2 ... P_k} \right \rfloor Sp1Sp2Sp3...Spk=p1p2...Pkn对于每个集合计算时间复杂度为 O ( k ) O(k) O(k),所以整个算法时间复杂度为 O ( m 2 m ) O(m2^m) O(m2m)

    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    typedef long long LL;
    
    const int N = 20;
    
    int p[N];
    
    int main() {
        int n, m; cin >> n >> m;
        for (int i = 0; i < m; i++) cin >> p[i];
    
        int res = 0;
        for (int i = 1; i < 1 << m; i++) {
            int t = 1, s = 0; // t表示所有质数乘积 s表示当前二进制数中1的个数
            for (int j = 0; j < m; j++)
                if (i >> j & 1) {
                    if ((LL) t * p[j] > n) {
                        t = -1;
                        break;
                    }
                    t *= p[j];
                    s++;
                }
    
            if (t != -1) {
                if (s % 2) res += n / t;
                else res -= n / t;
            }
        }
        cout << res << endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    五、博弈论

    NIM博弈

    给定 N N N堆物品,第 i i i堆物品有 A i A_i Ai个。两名玩家轮流行动,每次可以任选一堆,取走任意多个物品,可把一堆取光,但不能不取。取走最后一件物品者获胜。两人都采取最优策略,问先手是否必胜。

    我们把这种游戏称为NIM博弈。把游戏过程中面临的状态称为局面。整局游戏第一个行动的称为先手,第二个行动的称为后手。若在某一局面下无论采取何种行动,都会输掉游戏,则称该局面必败。

    所谓采取最优策略是指,若在某一局面下存在某种行动,使得行动后对面面临必败局面,则优先采取该行动。同时,这样的局面被称为必胜。我们讨论的博弈问题一般都只考虑理想情况,即两人均无失误,都采取最优策略行动时游戏的结果。

    NIM博弈不存在平局,只有先手必胜和先手必败两种情况。

    定理

    NIM博弈,若先手必胜,当且仅当 A 1   x o r   A 2   x o r   …   x o r   A n ≠ 0 A_1 \space xor \space A_2 \space xor \space … \space xor \space A_n \ne 0 A1 xor A2 xor  xor An=0反之,若先手必败,当且仅当 A 1   x o r   A 2   x o r   …   x o r   A n = 0 A_1 \space xor \space A_2 \space xor \space … \space xor \space A_n = 0 A1 xor A2 xor  xor An=0

    定理证明:

    • A 1 = A 2 = . . . = A n = 0 A_1 = A_2 = ... = A_n = 0 A1=A2=...=An=0时,有 A 1   x o r   A 2   x o r   . . .   x o r   A n = 0 A_1\space xor \space A_2\space xor \space ... \space xor \space A_n = 0 A1 xor A2 xor ... xor An=0,显然先手必败;
    • A 1   x o r   A 2   x o r   . . .   x o r   A n = x ≠ 0 A_1\space xor \space A_2\space xor \space ... \space xor \space A_n = x \ne 0 A1 xor A2 xor ... xor An=x=0时,先手一定可以通过若干操作使其异或值变成 0 0 0。假设 x x x二进制表示中最高的一位 1 1 1在第 k k k位,表示 A 1 , A 2 , . . . , A n A_1,A_2,...,A_n A1,A2,...,An中必存在一个数 A i A_i Ai的第 k k k位二进制位为 1 1 1,就会有 A i   x o r   x < A i A_i \space xor \space x < A_i Ai xor x<Ai。所以可以从第 i i i堆石子中拿走 A i − ( A i   x o r   x ) A_i - (A_i \space xor \space x) Ai(Ai xor x)个石子,于是第 i i i的石子数目变成 A i − ( A i − ( A i   x o r   x ) ) = A i   x o r   x A_i - (A_i - (A_i \space xor \space x)) = A_i \space xor \space x Ai(Ai(Ai xor x))=Ai xor x于是就有 A 1   x o r   A 2   x o r   …   x o r   A i   x o r   x   x o r   A i + 1 . . .   x o r   A n = x   x o r   x = 0
      A1 xor A2 xor  xor Ai xor x xor Ai+1... xor An=x xor x=0
      A1 xor A2 xor  xor Ai xor x xor Ai+1... xor An=x xor x=0
      所以对于先手而言,一定可以存在某种操作,使得剩余的石子的异或值为 0 0 0
    • A 1   x o r   A 2   x o r   . . .   x o r   A n = 0 A_1\space xor \space A_2\space xor \space ... \space xor \space A_n= 0 A1 xor A2 xor ... xor An=0时,不管如何操作,剩余石子的异或值都一定不是 0 0 0。反证法:假设从第 i i i堆中拿走若干石子后,其值变成 A i ′ A_i^{'} Ai剩余石子异或值为 0 0 0,则有 A 1   x o r   A 2   x o r   . . .   x o r   A n = 0 A 1   x o r   A 2   x o r   …   x o r   A i ′   x o r   A i + 1 . . .   x o r   A n = 0
      A1 xor A2 xor ... xor An=0A1 xor A2 xor  xor Ai xor Ai+1... xor An=0
      A1 xor A2 xor ... xor An=0A1 xor A2 xor  xor Ai xor Ai+1... xor An=0
      将上面两个式子左右异或可得 A i   x o r   A i ′ = 0 A_i \space xor \space A_i^{'} = 0 Ai xor Ai=0因为 A i ′ < A I A_i^{'} < A_I Ai<AI,因此上式矛盾,所以有当 A 1   x o r   A 2   x o r   . . .   x o r   A n = 0 A_1\space xor \space A_2\space xor \space ... \space xor \space A_n= 0 A1 xor A2 xor ... xor An=0时,不管如何操作,剩余石子的异或值都一定不是 0 0 0

    所以根据上面三个性质可以知道:

    • 如果先手开始操作的时候石子异或值不为 0 0 0,那么先手一定可以通过操作将其变成 0 0 0给后手。后手不管怎么操作,它操作后的石子的异或值一定不等于 0 0 0。同理,先手又可以通过操作使其异或值变成 0 0 0,后手又无论如何怎么操作石子,都会使其异或值变成 0 0 0给先手…等于说先手开始操作石子的时候,石子的异或值一定不是 0 0 0,后手开始操作的时候,石子的异或值一定是 0 0 0,由于石子一定有被拿完的那一刻即游戏结束,所以先手必胜,后手必败。
    • 如果先手开始操作的时候石子异或值是 0 0 0,先手操作后一定不是 0 0 0,后手操作后一定是 0 0 0,…,一样的推导下去,到最后游戏结束的时候,先手必败,后手必胜。

    公平组合游戏ICG

    若一个游戏被称为公平组合游戏,应该满足:

    • 由两名玩家交替行动;
    • 在游戏进程的任意时刻,可以执行的合法行动与轮到哪名玩家无关;
    • 不能行动的玩家判负;

    NIM博弈属于公平组合游戏,但城建的棋类游戏,比如围棋,就不是公平组合游戏。因为围棋交战双方分别只能落黑子和白子,胜负判定也比较复杂,不满足条件2和条件3。

    有向图游戏

    给定一个有向无环图,图中有一个唯一的起点,在起点上放有一枚棋子。两名玩家交替地把这枚棋子沿有向边进行移动,每次可以移动一步,无法移动者判负。该游戏被称为有向图游戏。

    任何一个公平组合游戏都可以转化为有向图游戏。具体方法是,把每个局面看成图中的一个节点,并且从每个局面向沿着合法行动能够到达的下一个局面连有向边。

    Mex运算

    S S S表示一个非负整数集合。定义 m e x ( S ) mex(S) mex(S)为求出不属于集合 S S S的最小非负整数的运算,即: m e x ( S ) = m i n ( x ) mex(S) = min(x) mex(S)=min(x) x ∈ N ( 自 然 数 ) x \in N(自然数) xN(),且 x ∉ S x \notin S x/S

    SG函数

    在有向图游戏中,对于每个节点 x x x,设从 x x x出发共有 k k k条有向边,分别到达节点 y 1 , y 2 , … , y k y_1, y_2, …, y_k y1,y2,,yk,定义 S G ( x ) SG(x) SG(x) x x x的后继节点 y 1 , y 2 , … , y k y_1, y_2, …, y_k y1,y2,,yk S G SG SG函数值构成的集合再执行 m e x ( S ) mex(S) mex(S)运算的结果,即: S G ( x ) = m e x ( S G ( y 1 ) , S G ( y 2 ) , … , S G ( y k ) ) SG(x) = mex({SG(y_1), SG(y_2), …, SG(y_k)}) SG(x)=mex(SG(y1),SG(y2),,SG(yk))特别地,整个有向图游戏 G G G S G SG SG函数值被定义为有向图游戏起点 s s s S G SG SG函数值,即 S G ( G ) = S G ( s ) SG(G) = SG(s) SG(G)=SG(s),有向图游戏 G G G的终点(没有出边的点) p p p S G SG SG函数定义为 S G ( p ) = 0 SG(p)=0 SG(p)=0
    在这里插入图片描述

    有向图游戏的和

    G 1 , G 2 , … , G m G_1, G_2, …, G_m G1,G2,,Gm m m m个有向图游戏。定义有向图游戏 G G G,它的行动规则是任选某个有向图游戏 G i G_i Gi,并在 G i G_i Gi上行动一步。 G G G被称为有向图游戏 G 1 , G 2 , … , G m G_1, G_2, …, G_m G1,G2,,Gm的和。有向图游戏的和的 S G SG SG函数值等于它包含的各个子游戏 S G SG SG函数值的异或和,即:
    S G ( G ) = S G ( G 1 )   x o r   S G ( G 2 )   x o r   …   x o r   S G ( G m ) SG(G) = SG(G1) \space xor \space SG(G2) \space xor \space … \space xor \space SG(Gm) SG(G)=SG(G1) xor SG(G2) xor  xor SG(Gm)

    定理

    • 有向图游戏的某个局面必胜,当且仅当该局面对应节点的 S G SG SG函数值大于 0 0 0
    • 有向图游戏的某个局面必败,当且仅当该局面对应节点的 S G SG SG函数值等于 0 0 0

    定理说明:当只有一个有向图的时候,有向图的终点是 0 0 0

    • 如果某个局面 S G ≠ 0 SG \ne 0 SG=0,即先手可以进行操作将异或值变成 0 0 0,后面根NIM游戏的定理一样,先手永远是非 0 0 0,后手永远是 0 0 0。有图可以知道非 0 0 0的局面一定可以到达 0 0 0的结束局面,因此先手必胜,后手必败。
    • 如果某个局面 S G = 0 SG = 0 SG=0,先手永远是 0 0 0,后手永远是非 0 0 0,由图可知,一开始为 0 0 0的状态,后面到不了其它非 0 0 0状态,所以后面结束的状态一定是 0 0 0,所以先手必败,后手必胜。

    对于多个有向图,则可以将所有图起点的 S G SG SG值异或起来。
    定理的证明同NIM游戏定理的证明。

    5.1 Nim游戏

    ACwing 891

    对于样例,有两堆石子,每堆分别有石子 2 、 3 2、3 23个。在NIM游戏中,先手必胜。原因是:先手如果先拿掉第二堆中的一个石子,则两堆石子数量一样,变成 2 、 2 2、2 22。后面的游戏中,对于先手而言,他只需要和后手拿的石子数量一样,这样就可以保证只要后手有石子拿,那么先手就有石子拿,并且拿完后两堆石子依然保持一样,所以可以推出来先手必胜。

    如果使用NIM游戏的定理,则代码非常简单。

    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    int main() {
        int n;
        scanf("%d", &n);
        int res = 0;
        while (n--) {
            int x;
            scanf("%d", &x);
            res ^= x;
        }
        if (res) puts("Yes");
        else puts("No");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    5.2 台阶-Nim游戏

    ACwing 892

    先给结论:如果对所有第 k k k堆石子,其中 k k k为奇数,有 a 1 ∩ a 3 ∩ . . . ∩ a n = x ≠ 0 a_1 \cap a_3 \cap ... \cap a_n =x \ne 0 a1a3...an=x=0,先手必胜,否则先手必败。

    • 假设 x ≠ 0 x \ne 0 x=0,由之前的结论可知,先手操作一定可以使 x = 0 x=0 x=0
      • 此时,后手如果从偶数台阶 u 4 u_4 u4拿走石子放到下一级奇数台阶 u 3 u_3 u3,那么先手可以将其拿到台阶 u 3 u_3 u3的石子继续放到下一级偶数台阶 u 2 u_2 u2上,这样就可以保证奇数台阶上的石子数量是不变的。所以先手抛给后手永远是一个 a 1 ∩ a 3 ∩ . . . ∩ a n = 0 a_1 \cap a_3 \cap ... \cap a_n = 0 a1a3...an=0的情况,先手自己得到的永远是 a 1 ∩ a 3 ∩ . . . ∩ a n ≠ 0 a_1 \cap a_3 \cap ... \cap a_n \ne 0 a1a3...an=0的情况,所以先手必胜。
      • 反之,如果此时后手是从奇数台阶拿走石子,由之前的结论可知,这个时候 a 1 ∩ a 3 ∩ . . . ∩ a n ≠ 0 a_1 \cap a_3 \cap ... \cap a_n \ne 0 a1a3...an=0,然后先手可以通过某种操作使其 a 1 ∩ a 3 ∩ . . . ∩ a n = 0 a_1 \cap a_3 \cap ... \cap a_n = 0 a1a3...an=0。因此对于先手一定是 a 1 ∩ a 3 ∩ . . . ∩ a n ≠ 0 a_1 \cap a_3 \cap ... \cap a_n \ne 0 a1a3...an=0,后手一定是 a 1 ∩ a 3 ∩ . . . ∩ a n = 0 a_1 \cap a_3 \cap ... \cap a_n = 0 a1a3...an=0,所以先手必胜。
    • 假设 x = 0 x=0 x=0,分析同上。
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    int main() {
        int n; scanf("%d", &n);
        int res = 0;
        for (int i = 1; i <= n; i++) {
            int x; scanf("%d", &x);
            if (i & 1) res ^= x; // 判别奇偶
        }
        if (res) puts("Yes");
        else puts("No");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    5.3 集合-Nim游戏

    ACwing 893

    假设现在仅有一堆石子,其有石子 10 10 10个,每次只能从中取 2 2 2 5 5 5个石子,则可以建立有向图
    在这里插入图片描述
    对于图中每个点,可以求出 S G SG SG值,如图所示
    在这里插入图片描述
    对于 n n n堆石子,可以使用同样的方式建立有向图并求出每个点的 S G SG SG值。然后对每个图中起点的 S G SG SG值进行异或运算,运用定理判定结果。

    #include <cstring>
    #include <iostream>
    #include <algorithm>
    #include <unordered_set>
    
    using namespace std;
    
    const int N = 110, M = 10010; // N堆石子,每堆M个
    
    int n, m;
    int s[N], f[M]; // s每堆石子个数 f表示sg函数的值
    
    int sg(int x) {
        if (f[x] != -1) return f[x]; // 如果被算过,直接返回,每个状态仅计算一次,最多计算M次
        unordered_set<int> S; // 当前状态能够到达的状态
        for (int i = 0; i < m; i++) {
            int sum = s[i];
            if (x >= sum) S.insert(sg(x - sum));
        }
        for (int i = 0;; i++) // 求出每个点的SG
            if (!S.count(i))
                return f[x] = i;
    }
    
    int main() {
        cin >> m; for (int i = 0; i < m; i++) cin >> s[i];
        cin >> n;
        memset(f, -1, sizeof f);
        int res = 0;
        for (int i = 0; i < n; i++) {
            int x; cin >> x;
            res ^= sg(x);
        }
        if (res) puts("Yes");
        else puts("No");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    5.4 拆分-Nim游戏

    ACwing 894

    首先,因为堆的规模是不断减小的,所以游戏最后一定可以结束。

    所以对每一个堆,可以求出该堆的每一个局面 S G SG SG函数,最终将所有起点的 S G SG SG函数值进行异或运算即可。

    对于每一个堆的 S G SG SG函数,每一个局面可以分成若干个两堆(即每两堆为一组),如下图。
    在这里插入图片描述

    对于 s g ( b 1 , b 2 ) sg(b_1,b_2) sg(b1,b2)可以由下式得到 s g ( b 1 , b 2 ) = s g ( b 1 )   x o r   s g ( b 2 ) sg(b_1,b_2) = sg(b_1) \space xor \space sg(b_2) sg(b1,b2)=sg(b1) xor sg(b2)

    #include <cstring>
    #include <iostream>
    #include <algorithm>
    #include <unordered_set>
    
    using namespace std;
    
    const int N = 110;
    
    int n;
    int f[N];
    
    int sg(int x) {
        if (f[x] != -1) return f[x];
        unordered_set<int> S;
        for (int i = 0; i < x; i++)
            for (int j = 0; j <= i; j++)
                S.insert(sg(i) ^ sg(j));
        for (int i = 0;; i++)
            if (!S.count(i)) return f[x] = i;
    }
    
    int main() {
        cin >> n;
        memset(f, -1, sizeof f);
        int res = 0;
        while (n--) {
            int x; cin >> x;
            res ^= sg(x);
        }
        if (res) puts("Yes");
        else puts("No");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
  • 相关阅读:
    使用AES-128-CBC 加密用户名和密码 完成登陆 前端Vue 后端springboot
    存在主义和阳明心学
    Windows操作系统进阶:防火墙基础和Windows Defender
    java解析生成定时Cron表达式工具类
    Docker入门指南:基于 docker 搭建机器学习/深度学习开发环境
    GO操作mongodb实现增删改查带分页
    【回眸】英飞凌TC397测试Jupiter源代码
    JAVA基础算法(10)----- 层数最深叶子节点的和
    cpu常用命令
    css盒子模型(框模型)属性
  • 原文地址:https://blog.csdn.net/qq_34696503/article/details/125414556