算法基础课——第四章 数学知识(四)



第四章 数学知识(四)


如无特殊说明,所有数均为正整数.


容斥原理


例如下维恩图:


维恩图图示

如果想要求出左图三个圆覆盖的面积时,设黑边圆的面积为 S k S_k Sk,红边圆的面积为 S r S_r Sr,蓝边圆的面积为 S b S_b Sb,但 S k + S r + S b S_k+S_r+S_b Sk+Sr+Sb 并不是三个圆覆盖的面积,其有一些重叠的面积经过了多次计算.

中间图所示,两个圆共同覆盖的面积为灰色部分,则设黑边圆与红边圆共同覆盖的面积为 S k , r = S k ∩ S r S_{k,r}=S_k\cap S_r Sk,r=SkSr,黑边圆与蓝边圆共同覆盖的面积为 S k , b = S k ∩ S b S_{k,b}=S_k\cap S_b Sk,b=SkSb,红边圆与蓝边圆共同覆盖的面积为 S r , b = S r ∩ S b S_{r,b}=S_r\cap S_b Sr,b=SrSb.

右图所示,设三个圆共同覆盖的面积为 S k , r , b = S k ∩ S r ∩ S b S_{k,r,b}=S_k\cap S_r \cap S_b Sk,r,b=SkSrSb.

故三个圆覆盖的面积为:
S = S k + S r + S b − S k , r − S k , b − S r , b + S k , r , b S=S_k+S_r+S_b-S_{k,r}-S_{k,b}-S_{r,b}+S_{k,r,b} S=Sk+Sr+SbSk,rSk,bSr,b+Sk,r,b

因为当 S 1 = S k + S r + S b S_1=S_k+S_r+S_b S1=Sk+Sr+Sb 时:

  • 两个圆共同覆盖的面积 S k , b , S k , r , S r , b S_{k,b},S_{k,r},S_{r,b} Sk,b,Sk,r,Sr,b非共同部分被计算了 2 2 2 次(共同部分即为 S k , r , b S_{k,r,b} Sk,r,b);
  • 三个圆共同覆盖的面积 S k , r , b S_{k,r,b} Sk,r,b 被计算了 3 3 3 次.

所以需要先将两个圆共同覆盖的面积都减去 1 1 1 次,此时 S 2 = S k + S r + S b − S k , r − S k , b − S r , b S_2=S_k+S_r+S_b-S_{k,r}-S_{k,b}-S_{r,b} S2=Sk+Sr+SbSk,rSk,bSr,b

  • 虽然两个圆共同覆盖的面积的非共同部分此时只计算了 1 1 1 次,但是其共同部分 S k , r , b S_{k,r,b} Sk,r,b 总共被减去了 3 3 3 次,此时它的面积没有被算进 S 2 S_2 S2 内.

故需要将 S k , r , b S_{k,r,b} Sk,r,b 补充到里面,故有:
S = S k + S r + S b − S k , r − S k , b − S r , b + S k , r , b S=S_k+S_r+S_b-S_{k,r}-S_{k,b}-S_{r,b}+S_{k,r,b} S=Sk+Sr+SbSk,rSk,bSr,b+Sk,r,b

这里做一下推广:

如果只有两个圆 S 1 , S 2 S_1,S_2 S1,S2,则两个圆的面积为 S = S 1 + S 2 − S 1 ∩ S 2 S=S_1+S_2-S_1\cap S_2 S=S1+S2S1S2

如果是四个圆 S 1 , S 2 , S 3 , S 4 S_1,S_2,S_3,S_4 S1,S2,S3,S4,则面积为:
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 2 ∩ S 4 + S 1 ∩ S 3 ∩ S 4 + S 2 ∩ S 3 ∩ S 4 − S 1 ∩ S 2 ∩ S 3 ∩ S 4 \begin{aligned} S & =S_1+S_2+S_3+S_4 \\ & -S_1\cap S_2-S_1\cap S_3 - S_1\cap S_4 - S_2 \cap S_3 - S_2 \cap S_4 - S_3 \cap S_4 \\ & + S_1 \cap S_2 \cap S_3 + S_1 \cap S_2 \cap S_4 + S_1 \cap S_3 \cap S_4 + S_2\cap S_3 \cap S_4 \\ & -S_1 \cap S_2 \cap S_3 \cap S_4 \end{aligned} S=S1+S2+S3+S4S1S2S1S3S1S4S2S3S2S4S3S4+S1S2S3+S1S2S4+S1S3S4+S2S3S4S1S2S3S4
则对于 n n n 个圆来说,则面积为:
S = S 1 个 圆 − S 2 个 圆 + S 3 个 圆 + ⋯ + ( − 1 ) n − 1 S n 个 圆 S=S_{1个圆}-S_{2个圆}+S_{3个圆}+\cdots+(-1)^{n-1}S_{n个圆} S=S1S2+S3++(1)n1Sn

  • 其中 S k 个 圆 S_{k个圆} Sk 表示所有 k k k 个圆的面积的并集( ∩ \cap )之和.

  • 一共有 ( n 1 ) + ( n 2 ) + ⋯ + ( n n ) = 2 n − 1 \begin{pmatrix}n\\1\end{pmatrix}+\begin{pmatrix}n\\2\end{pmatrix}+\cdots+\begin{pmatrix}n\\n\end{pmatrix}=2^n-1 (n1)+(n2)++(nn)=2n1 项.

    • 可以看作从 n n n 个数中选取任意多个数的方案数,每个数都有选与不选两种情况,总共为 2 n 2^n 2n 种方案,去掉什么数都不选的那种,即为 2 n − 1 2^n-1 2n1 项;
    • 也可以设 ( 1 + x ) n = ( n 0 ) + ( n 1 ) x + ( n 2 ) x 2 + ⋯ + ( n n ) x n (1+x)^n=\begin{pmatrix}n\\0\end{pmatrix}+\begin{pmatrix}n\\1\end{pmatrix}x+\begin{pmatrix}n\\2\end{pmatrix}x^2+\cdots+\begin{pmatrix}n\\n\end{pmatrix}x^n (1+x)n=(n0)+(n1)x+(n2)x2++(nn)xn,此时令 x = 1 x=1 x=1,即可得到 ( n 0 ) + ( n 1 ) + ( n 2 ) + ⋯ + ( n n ) = 2 n \begin{pmatrix}n\\0\end{pmatrix}+\begin{pmatrix}n\\1\end{pmatrix}+\begin{pmatrix}n\\2\end{pmatrix}+\cdots+\begin{pmatrix}n\\n\end{pmatrix}=2^n (n0)+(n1)+(n2)++(nn)=2n
    • 组合恒等式的证明,一般从实际意义出发,可以从多种不同的角度,得到相同的答案.
    • 故容斥原理的时间复杂度为 O ( 2 n ) O(2^n) O(2n).
  • 为什么会有 ( − 1 ) n − 1 (-1)^{n-1} (1)n1 呢?

    • 证明
    • 对于 ∣ ⋃ i = 1 n S i ∣ = ∑ i S i − ∑ i , j ∣ S i ∩ S j ∣ + ∑ i , j , k ∣ S i ∩ S j ∩ S k ∣ + ⋯ |\bigcup_{i=1}^nS_i|=\sum_{i}S_i-\sum_{i,j}|S_{i}\cap S_j|+\sum_{i,j,k}|S_i\cap S_j \cap S_k|+\cdots i=1nSi=iSii,jSiSj+i,j,kSiSjSk+,若等式左边的任何一个元素 S i S_i Si,在等式右边只被算了一次,则可以说明容斥原理成立.
    • 这里 ∣ S ∣ |S| S 代表是 S S S 的大小,在这里的意义是面积.
    • 不妨设对于 ∣ ⋃ i = 1 n S i ∣ |\bigcup_{i=1}^nS_i| i=1nSi 的任意一部分 ∣ S x ∣ |S_x| Sx,其在等式右边的每个和式 ∑ \sum 中出现了 k    ( 1 ⩽ k ⩽ n ) k\;(1\leqslant k \leqslant n) k(1kn) 次,则其总共计算了 ( k 1 ) − ( k 2 ) + ( k 2 ) + ⋯ + ( − 1 ) k − 1 ( k k ) = 1 \begin{pmatrix}k\\1\end{pmatrix}-\begin{pmatrix}k\\2\end{pmatrix}+\begin{pmatrix}k\\2\end{pmatrix}+\cdots+(-1)^{k-1}\begin{pmatrix}k\\k\end{pmatrix}=1 (k1)(k2)+(k2)++(1)k1(kk)=1 次;
      • 首先,这里的 ∣ S x ∣ |S_x| Sx 可以理解为 n n n 个圆的面积中的某一部分,而 k k k 取决于有多少个圆在这一部分有交集;
      • 其次,对于 ( k 1 ) − ( k 2 ) + ( k 2 ) + ⋯ + ( − 1 ) k − 1 ( k k ) = 1 \begin{pmatrix}k\\1\end{pmatrix}-\begin{pmatrix}k\\2\end{pmatrix}+\begin{pmatrix}k\\2\end{pmatrix}+\cdots+(-1)^{k-1}\begin{pmatrix}k\\k\end{pmatrix}=1 (k1)(k2)+(k2)++(1)k1(kk)=1,则对于 ( 1 + x ) k (1+x)^k (1+x)k,令 x = − 1 x=-1 x=1 即可得到 [ 1 + ( − 1 ) ] k = ( k 0 ) − ( k 1 ) + ( k 2 ) + ⋯ + ( − 1 ) k − 1 ( k k ) = 0 [1+(-1)]^k=\begin{pmatrix}k\\0\end{pmatrix}-\begin{pmatrix}k\\1\end{pmatrix}+\begin{pmatrix}k\\2\end{pmatrix}+\cdots+(-1)^{k-1}\begin{pmatrix}k\\k\end{pmatrix}=0 [1+(1)]k=(k0)(k1)+(k2)++(1)k1(kk)=0,但由于 ( k 0 ) = 1 \begin{pmatrix}k \\ 0 \end{pmatrix}=1 (k0)=1,所以 − ( k 1 ) + ( k 2 ) + ⋯ + ( − 1 ) k ( k k ) = − 1 -\begin{pmatrix}k\\1\end{pmatrix}+\begin{pmatrix}k\\2\end{pmatrix}+\cdots+(-1)^{k}\begin{pmatrix}k\\k\end{pmatrix}=-1 (k1)+(k2)++(1)k(kk)=1,同时乘 − 1 -1 1 即可得到 ( k 1 ) − ( k 2 ) + ( k 2 ) + ⋯ + ( − 1 ) k − 1 ( k k ) = 1 \begin{pmatrix}k\\1\end{pmatrix}-\begin{pmatrix}k\\2\end{pmatrix}+\begin{pmatrix}k\\2\end{pmatrix}+\cdots+(-1)^{k-1}\begin{pmatrix}k\\k\end{pmatrix}=1 (k1)(k2)+(k2)++(1)k1(kk)=1.
      • ( − 1 ) k + 1 = ( − 1 ) k − 1 (-1)^{k+1}=(-1)^{k-1} (1)k+1=(1)k1.
    • 所以可以得证 n n n 个圆的面积只被计算了一次.
    • 证毕

算法题中涉及到的算法知识绝大多数都是离散数学中的内容.

AcWing 890. 能被整除的数

原题链接

给定一个整数 n n n m m m 个不同的质数 p 1 , p 2 , ⋯   , p m p_1,p_2,\cdots,p_m p1,p2,,pm

请你求出 1 ∼ n 1∼n 1n 中能被 p 1 , p 2 , ⋯   , p m p_1,p_2,\cdots,p_m p1,p2,,pm 中的至少一个数整除的整数有多少个。

输入格式

第一行包含整数 n n n m m m

第二行包含 m m m 个质数。

输出格式

输出一个整数,表示满足条件的整数的个数。

数据范围

1 ≤ m ≤ 16 1≤m≤16 1m16,
1 ≤ n , p i ≤ 1 0 9 1≤n,p_i≤10^9 1n,pi109

输入样例:

10 2
2 3

输出样例:

7

时/空限制: 1s / 64MB
来源: 模板题
算法标签:容斥原理

yxc’s Solution

样例中,在 10 10 10 以内能被 2 2 2 或者 3 3 3 整除的数为 2 , 3 , 4 , 6 , 8 , 9 , 10 2,3,4,6,8,9,10 2,3,4,6,8,9,10,共 7 7 7 个.

  • 考虑使用容斥原理,定义集合 S i S_i Si 1 ∼ n 1\sim n 1n 中所有能被 i i i 整除的数.

样例中,共有两个集合 S 2 = { 2 , 4 , 6 , 8 , 10 } , S 3 = { 3 , 6 , 9 } S_2=\{2,4,6,8,10\},S_3=\{3,6,9\} S2={2,4,6,8,10},S3={3,6,9}.

  • 所求答案即为 ∣ ⋃ S i ∣ |\bigcup S_i| Si.

样例中,答案即为 ∣ S 2 ∪ S 3 ∣ = ∣ S 2 ∣ + ∣ S 3 ∣ − ∣ S 2 ∩ S 3 ∣ = 5 + 3 − 1 = 7 |S_2\cup S_3|=|S_2|+|S_3|-|S_2\cap S_3|=5+3-1=7 S2S3=S2+S3S2S3=5+31=7.

  • 如何求 ∣ S p ∣ |S_p| Sp
    • ∣ S p ∣ = ⌊ n p ⌋ |S_p|=\lfloor\frac{n}{p}\rfloor Sp=pn.
      • 因为 1 ∼ n 1\sim n 1n 中存在一个最大的数 k p ⩽ n ,    k ∈ N kp \leqslant n,\;k\in \mathbf{N} kpn,kN p p p 的倍数,则说明 p , 2 p , 3 p , ⋯   , k p p,2p,3p,\cdots,kp p,2p,3p,,kp 1 ∼ n 1\sim n 1n 中都存在,一共有 k k k 个,而 k = ⌊ n p ⌋ k=\lfloor\frac{n}{p}\rfloor k=pn.
    • ∣ S p 1 ∩ S p 2 ∣ = ⌊ n p 1 p 2 ⌋ |S_{p_1}\cap S_{p_2}|=\lfloor\frac{n}{p_1p_2}\rfloor Sp1Sp2=p1p2n.
  • 所以算 k k k 个集合的交集的时间复杂度是 O ( k ) O(k) O(k) 的,所以总的时间复杂度为 O ( 2 m × m ) O(2^m \times m) O(2m×m).
  • 代码实现上,可以用DFS深度优先搜索来写,也可以位运算来枚举所有集合.
    • 用长度为 n n n 的二进制数表示每一个集合交集;若某位上为 0 0 0 则不取某集合,为 1 1 1 则取该集合;选取集合数量如果为奇数,则符号为 + + +,如果为偶数,则符号为 + + +.
    • 这样,从 1 1 1 枚举到 2 m − 1 2^m-1 2m1,就可以把所有的集合交集都处理出来了.
#include
#include
using namespace std;
const int N=17;
int n,m,p[N];
int main(){
    scanf("%d%d",&n, &m);
    for(int i=0;i<m;++i) scanf("%d",&p[i]);

    int res=0;
    for(int i=1;i<(1<<m);++i){
        int cnt=0; // 当前集合交集包含多少个集合
        int t=1;    // 所有质数的乘积
        for(int j=0;j<m;++j)
            if((i>>j)&1){
                ++cnt;
                if(1ll*t*p[j]>n){ t=-1; break; }
                t*=p[j];
            }
        if(t!=-1){
            if(cnt%2) res+=n/t;
            else res-=n/t;
        }
    }

    printf("%d\n",res);
    return 0;
}

博弈论


以下内容摘自《算法竞赛进阶指南》(第6次印刷).

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 \;xor\; A_2\;xor\;\cdots\;xor\;A_n \not=0 A1xorA2xorxorAn=0.

证明

所有物品都被取光是一个必败局面(对手取走最后一个物品,已经获得胜利),此时显然有 A 1    x o r    A 2    x o r    ⋯    x o r    A n = 0 A_1 \;xor\; A_2\;xor\;\cdots\;xor\;A_n =0 A1xorA2xorxorAn=0.

对于任意一个局面,如果 A 1    x o r    A 2    x o r    ⋯    x o r    A n = x ≠ 0 A_1 \;xor\; A_2\;xor\;\cdots\;xor\;A_n =x \not =0 A1xorA2xorxorAn=x=0​,设 x x x​ 的二进制表示下最高位的 1 1 1​ 在第 k k k​ 位,那么至少存在一堆石子 A i A_i Ai​,它的第 k k k​ 位是 1 1 1​,显然 A i    x o r    x < A i A_i \; xor \; x< A_i Aixorx<Ai​,我们就从 A i A_i Ai​ 堆中取走若干石子,使其变为 A i    x o r    x A_i \; xor \; x Aixorx​,就得到了一个各堆石子数异或起来等于 0 0 0​ 的局面.

对于任意一个局面,如果 A 1    x o r    A 2    x o r    ⋯    x o r    A n = 0 A_1 \;xor\; A_2\;xor\;\cdots\;xor\;A_n =0 A1xorA2xorxorAn=0,那么无论如何取石子,得到的局面下各堆石子异或起来都不等于 0 0 0。可用反证法证明,假设 A i A_i Ai 被取成了 A i ′ A_i' Ai,并且 A 1    x o r    A 2    x o r    ⋯    x o r A i    x o r    ⋯    x o r    A n = 0 A_1 \;xor\; A_2\;xor\;\cdots \;xor A_i \; xor\; \cdots \;xor\;A_n =0 A1xorA2xorxorAixorxorAn=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 \;xor\; A_2\;xor\;\cdots\;xor\;A_n \not=0 A1xorA2xorxorAn=0 为必胜局面,一定存在一种行动让对手面临 “各堆石子异或起来等于 0 0 0”。 A 1    x o r    A 2    x o r    ⋯    x o r    A n = 0 A_1 \;xor\; A_2\;xor\;\cdots\;xor\;A_n =0 A1xorA2xorxorAn=0 为必败局面,无论如何行动,都会让对手面临一个 “各堆石子异或起来不等于 0 0 0​” 的必胜局面.

证毕

可以了解一下巴什博弈,推荐博客:简单易懂的博弈论讲解(巴什博弈、尼姆博弈、威佐夫博弈、斐波那契博弈、SG定理) - The_Virtuoso - 博客园 (cnblogs.com)

公平组合游戏ICG

若一个游戏满足:

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

这称该游戏为一个公平组合游戏.

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 ∈ N , x ∉ S { x } mex(S)=min_{x\in N,x \notin S}\{x\} mex(S)=minxN,x/S{x}.

SG函数

在有向图游戏中,对于每个节点 x x x,设从 x x x 出发共有 k k k 条有向边,分别到达节点 y 1 , y 2 , ⋯   , y k y_1,y_2,\cdots,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,\cdots,y_k y1,y2,,yk S G SG SG 函数值构成的集合再执行 m e x mex mex 运算的结果,即 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),\cdots,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).

S G ( SG( SG(​终点 ) = 0 )=0 )=0
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),\cdots,SG(y_k)\}) SG(x)=mex({SG(y1),SG(y2),,SG(yk)})


SG函数示例图

S G ( x ) = 0 SG(x)=0 SG(x)=0 必败, S G ( x ) ≠ 0 SG(x)\not=0 SG(x)=0 必胜.
因为如果 S G ( x ) ≠ 0 SG(x)\not=0 SG(x)=0,则说明 x x x 是可以到达 0 0 0 的,而如果 S G ( x ) = 0 SG(x)=0 SG(x)=0 则说明 x x x 是到不了 0 0 0 的(往下有进一步的证明).

Q:那既然 S G ( x ) ≠ 0 SG(x)\not=0 SG(x)=0 就必胜,为什么不定义非 0 0 0 即为 1 1 1 呢?
A:如果只有一个图,那确实是可以的;但对于复杂的问题就不行了.

有向图游戏的和

G 1 , G 2 , ⋯   , G m G_1,G_2,\cdots,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,\cdots,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(G_1) \; xor\; SG(G_2) \; xor \; \cdots \;xor \; SG(G_m) SG(G)=SG(G1)xorSG(G2)xorxorSG(Gm).

= 0 =0 =0​ 必败, ≠ 0 \not=0 =0​ 必胜.

定理

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

我们不再详细证明该定理。读者可以这样理解:

在一个没有出边的节点上,棋子不能移动,它的 S G SG SG 值为 0 0 0,对应必败局面;

若一个节点的某个后继节点 S G SG SG 值为 0 0 0,在 m e x mex mex 运算后,该节点的 S G SG SG 值大于 0 0 0。这等价于,若一个局面的后继局面中存在必败局面,则当前局面为必胜局面;

若一个节点的后继节点 S G SG SG 值均不为 0 0 0,在 m e x mex mex 运算后,该节点的 S G SG SG 值为 0 0 0。这等价于,若一个局面的后继局面全部为必胜局面,则当前局面为必败局面;

对于若干个有向图游戏的和,其证明方法与NIM博弈类似.

① ① 若 所有的 S G ( x i ) = 0 SG(x_i)=0 SG(xi)=0 ,则 S G ( G ) = S G ( G 1 )    x o r    S G ( G 2 )    x o r    ⋯    x o r    S G ( G m ) = 0 SG(G)= SG(G_1) \; xor\; SG(G_2) \; xor \; \cdots \;xor \; SG(G_m)=0 SG(G)=SG(G1)xorSG(G2)xorxorSG(Gm)=0,先手必败;

② ② S G ( G ) = S G ( G 1 )    x o r    S G ( G 2 )    x o r    ⋯    x o r    S G ( G m ) = x ≠ 0 SG(G)= SG(G_1) \; xor\; SG(G_2) \; xor \; \cdots \;xor \; SG(G_m) = x \not=0 SG(G)=SG(G1)xorSG(G2)xorxorSG(Gm)=x=0,则会存在 S G ( x i )    x o r    x < S G ( x i ) SG(x_i) \;xor\;xSG(xi)xorx<SG(xi),就一定可以走到 S G ( x i )    x o r    x SG(x_i) \;xor\;x SG(xi)xorx 这一局面,使 S G ( G ) = S G ( G 1 )    x o r    S G ( G 2 )    x o r    ⋯    x o r    S G ( G m ) = 0 SG(G)= SG(G_1) \; xor\; SG(G_2) \; xor \; \cdots \;xor \; SG(G_m)=0 SG(G)=SG(G1)xorSG(G2)xorxorSG(Gm)=0

③ ③ S G ( G ) = S G ( G 1 )    x o r    S G ( G 2 )    x o r    ⋯    x o r    S G ( G m ) = 0 SG(G)= SG(G_1) \; xor\; SG(G_2) \; xor \; \cdots \;xor \; SG(G_m)=0 SG(G)=SG(G1)xorSG(G2)xorxorSG(Gm)=0,利用反证法,通过消去律得到 S G ( x i ) = k → S G ( x i ) ′ = k SG(x_i)=k \to SG(x_i)'=k SG(xi)=kSG(xi)=k,但 S G ( x i ) SG(x_i) SG(xi) 自己都等于 k k k 了,不可能再走到 k k k 这一局面,所以不论怎么变,都会使 S G ( G ) = S G ( G 1 )    x o r    S G ( G 2 )    x o r    ⋯    x o r    S G ( G m ) ≠ 0 SG(G)= SG(G_1) \; xor\; SG(G_2) \; xor \; \cdots \;xor \; SG(G_m)\not=0 SG(G)=SG(G1)xorSG(G2)xorxorSG(Gm)=0.

AcWing 891. Nim游戏

原题链接

给定 n n n 堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

输入格式

第一行包含整数 n n n

第二行包含 n n n 个数字,其中第 i i i 个数字表示第 i i i 堆石子的数量。

输出格式

如果先手方必胜,则输出 Yes

否则,输出 No

数据范围

1 ≤ n ≤ 1 0 5 1≤n≤10^5 1n105,
1 ≤ 1≤ 1每堆石子数 ≤ 1 0 9 ≤10^9 109

输入样例:

2
2 3

输出样例:

Yes

时/空限制: 1s / 64MB
来源: 模板题
算法标签:数学知识 博弈论 Nim游戏

yxc’s Solution

  • 例如有两堆石子 2 , 3 2,3 2,3,先手从 3 3 3 的那堆拿 1 1 1 个石子,局面变成 2 , 2 2,2 2,2

  • 则后手不管拿多少石子,先手直接镜像地从另一堆石子中拿同样多个石子即可,可以发现,后手一定会最先遇到两堆石子都是 0 0 0 的局面,这样先手必胜;

  • 定理:NIM博弈先手必胜,当且仅当 A 1    x o r    A 2    x o r    ⋯    x o r    A n ≠ 0 A_1 \;xor\; A_2\;xor\;\cdots\;xor\;A_n \not=0 A1xorA2xorxorAn=0.

#include
#include
#include
using namespace std;
int main(){
	int n;
	int res=0;
	scanf("%d",&n);
	while(n--){
		int x;
		scanf("%d",&x);
		res^=x;
	}
	if(res) puts("Yes");
	else puts("No");
	return 0;
}

AcWing 892. 台阶-Nim游戏

原题链接

现在,有一个 n n n 级台阶的楼梯,每级台阶上都有若干个石子,其中第 i i i 级台阶上有 a i a_i ai 个石子( i ≥ 1 i≥1 i1)。

两位玩家轮流操作,每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶中(不能不拿)。

已经拿到地面上的石子不能再拿,最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

输入格式

第一行包含整数 n n n

第二行包含 n n n 个整数,其中第 i i i 个整数表示第 i i i 级台阶上的石子数 a i a_i ai

输出格式

如果先手方必胜,则输出 Yes

否则,输出 No

数据范围

1 ≤ n ≤ 1 0 5 1≤n≤10^5 1n105,
1 ≤ a i ≤ 1 0 9 1≤a_i≤10^9 1ai109

输入样例:

3
2 1 3

输出样例:

Yes

时/空限制: 1s / 64MB
来源: 模板题
算法标签:数学知识 博弈论 Nim游戏

yxc’s Solution

对于样例:


样例操作图示
图中红色操作为先手操作,橙色操作为后手操作,则:
  • 对于先手来说,只需要让 1 1 1 号台阶与 3 3 3 号台阶的石子数量保持相等即可,例如先手的第一次操作,是从 3 3 3 号台阶拿出 1 1 1 个石子放到 2 2 2 号台阶上,此时:
  1. 若后手从 2 2 2 号台阶上拿取 x x x 个石子放到 1 1 1 号台阶上,先手可以把这 x x x 个石子放到地面上,此时仍有 1 1 1 号台阶的石子数量等于 3 3 3 号台阶的石子数量;
  2. 若后手从 3 3 3 号台阶上拿取 x x x 个石子放到 2 2 2 号台阶上,先手可以从 1 1 1 号台阶上拿取 x x x 个石子放到地面上,此时仍有 1 1 1 号台阶的石子数量等于 3 3 3 号台阶的石子数量;

也就是说,对于先手,只要他保持,轮到自己时, 1 1 1 号台阶的石子数量与 3 3 3 号台阶的石子数量不等,就可以创造一个 1 1 1 号台阶的石子数量与 3 3 3 号台阶的石子数量相等局面,给后手;

而最终输掉的一方,一定面临着所有台阶上的石子之和为 0 0 0 的局面,此时显然 1 1 1 号台阶和 3 3 3 号台阶上没有石子了;

所以先手只要保证,轮到自己时, 1 1 1 号台阶的石子数量与 3 3 3 号台阶的石子数量不等,并且给后手的局面是一个 1 1 1 号台阶的石子数量与 3 3 3 号台阶的石子数量相等的局面,就一定不会到达这种最终输掉的局面;

而后手每次面临的局面,都是 1 1 1 号台阶的石子数量与 3 3 3 号台阶的石子数量相等的局面,则他早晚会面临 1 1 1 号台阶和 3 3 3 号台阶上没有石子的局面,此时不论 2 2 2 号台阶上有没有石子,他都必输.

  • 因为此时如果 2 2 2 号台阶上还有石子,后手搬多少到 1 1 1 号台阶,先手就可以搬多少到地面,后手显然必输.
  • 通过样例,可以推导出,只需要关心奇数级台阶上的石子;
  • 设所有奇数级台阶上的石子的异或和 a 1    x o r    a 3    x o r    ⋯ a 2 k + 1 = x ≠ 0 a_1\;xor\;a_3\;xor\;\cdots a_{2k+1}=x\not=0 a1xora3xora2k+1=x=0,则先手必胜;否则先手必输.
    • 证明
    • x ≠ 0 x\not=0 x=0,则已经证明过,一定存在一种操作方式,使得从其中的某个台阶上拿走若干个石子,让 x = 0 x=0 x=0
    • 如果后手从某个偶数级台阶拿取 k k k 个石子放到下一级台阶,则先手可以把这 k k k 个石子再往下放一级台阶,这样留给后手的局面仍是 x = 0 x=0 x=0
    • 如果后手从某个奇数级台阶拿取 k k k 个石子,则会造成 x ≠ 0 x\not=0 x=0,此时先手一定有办法将 x ≠ 0 x\not=0 x=0 的局面变为 x = 0 x=0 x=0 的局面;
    • 所以如果一开始 x ≠ 0 x\not=0 x=0,则先手一定有办法,让后手永远面临 x = 0 x=0 x=0 的局面;
    • 由于必输局面一定存在 x = 0 x=0 x=0,且若 x ≠ 0 x\not=0 x=0 说明还可以进行操作;故只要在一开始 x ≠ 0 x\not=0 x=0,则先手必胜.
    • 证毕
#include
#include
using namespace std;
int main(){
    int n,sum=0; scanf("%d",&n);
    for(int i=1;i<=n;++i){
        int a; scanf("%d",&a);
        if(i%2) sum^=a;
    }
    if(sum) puts("Yes");
    else puts("No");
    return 0;
}

AcWing 893. 集合-Nim游戏

原题链接

给定 n n n 堆石子以及一个由 k k k 个不同正整数构成的数字集合 S S S

现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合 S S S,最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

输入格式

第一行包含整数 k k k,表示数字集合 S S S 中数字的个数。

第二行包含 k k k 个整数,其中第 i i i 个整数表示数字集合 S S S 中的第 i i i 个数 s i s_i si

第三行包含整数 n n n

第四行包含 n n n 个整数,其中第 i i i 个整数表示第 i i i 堆石子的数量 h i h_i hi

输出格式

如果先手方必胜,则输出 Yes

否则,输出 No

数据范围

1 ≤ n , k ≤ 100 1≤n,k≤100 1n,k100,
1 ≤ s i , h i ≤ 10000 1≤s_i,h_i≤10000 1si,hi10000

输入样例:

2
2 5
3
2 4 7

输出样例:

Yes

时/空限制: 1s / 64MB
来源: 模板题
算法标签:数学知识 博弈论 SG函数

yxc’s Solution

  • 如果只有一堆石子的话,可以画出一个状态图,从而求出初始状态的 S G SG SG 值.

如只有一堆大小为 10 10 10 的石子,每次只能取 2 2 2 个或 5 5 5 个.


大小为 10 的一堆石子的SG函数示例图
  • n n n 堆就可以看作 n n n 个有向图.

  • 利用 S G SG SG 定理,将所有图的 S G SG SG 值异或起来就可以判定必胜或者必败.

#include
#include
#include
#include
#include
using namespace std;
const int N=110,M=10010;
int n,m;
int s[N],f[M]; //用 s 来储存集合 S,用 f 来表示 SG 值
//SG 值的求法本质上是利用了记忆化搜索
int sg(int x){
	//这就是记忆化,即某个数的 SG值 被算过的话,就可以直接返回,保证时间复杂度不至于过高
	//因为石子个数最多是 10000 ,而集合大小最大是 100,故所有状态全部计算的时间复杂度为 10^6
	if(f[x]!=-1) return f[x];
	//用哈希表存它可以到的局面
	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)
		if(!S.count(i)) return f[x]=i;
}
int main(){
	scanf("%d",&m);
	for(int i=0;i<m;++i) scanf("%d",&s[i]);
	scanf("%d",&n);
	memset(f,-1,sizeof f);
	int res=0;
	for(int i=0;i<n;++i){
		int x;
		scanf("%d",&x);
		res^=sg(x);
	}
	if(res) puts("Yes");
	else puts("No");
	return 0;
}

AcWing 894. 拆分-Nim游戏

原题链接

给定 n n n 堆石子,两位玩家轮流操作,每次操作可以取走其中的一堆石子,然后放入两堆规模更小的石子(新堆规模可以为 0 0 0,且两个新堆的石子总数可以大于取走的那堆石子数),最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

输入格式

第一行包含整数 n n n

第二行包含 n n n 个整数,其中第 i i i 个整数表示第 i i i 堆石子的数量 a i a_i ai

输出格式

如果先手方必胜,则输出 Yes

否则,输出 No

数据范围

1 ≤ n , a i ≤ 100 1≤n,a_i≤100 1n,ai100

输入样例:

2
2 3

输出样例:

Yes

时/空限制: 1s / 64MB
来源: 模板题
算法标签:数学知识 博弈论 SG函数

yxc’s Solution

  • 由于只要进行操作,石子堆的最大值就一定会不断减少,所以此游戏是一定可以停止的.

即对于大小为 x x x 的某个石子堆,最坏情况即为变成两堆大小为 x − 1 x-1 x1 石子堆,这样对此堆的操作过程就犹如一棵满二叉树,显然其操作次数为 2 x − 1 2^{x}-1 2x1

即对于大小为 x x x 的某个石子堆,它及经它操作后得到的石子堆,最多共计能进行 2 x − 1 2^{x}-1 2x1 次操作.

  • 对于每个石子堆,都单独求一个 S G SG SG 值,最后将它们异或起来,就可以得到整个局面的 S G SG SG 值.

对于某个大小为 a a a 的石子堆,其可以分成类似于 ( b i , b j ) ,    b i , b j < a (b_i,b_j),\;b_i,b_j (bi,bj),bi,bj<a 的多种局面,每种局面的 S G SG SG 值即为 S G ( b i )    x o r    S G ( b j ) SG(b_i) \; xor \; SG(b_j) SG(bi)xorSG(bj)

所以可以枚举 a a a 能到达的所有局面,算出所有局面的 S G SG SG 值,随后取 m e x mex mex 即可.

#include
#include
#include
#include
using namespace std;
const int N=110;
int sg[N];
int get_sg(int x){
    if(sg[x]!=-1) return sg[x];
    unordered_set<int> S;
    for(int i=0;i<x;++i)
        for(int j=0;j<=i;++j) //为了避免局面重复,所以约定第二堆的大小,小于等于第一堆的大小
            S.insert(get_sg(i)^get_sg(j));
    for(int i=0;;++i)
        if(!S.count(i)) return sg[x]=i;
}
int main(){
    int n,sum=0; scanf("%d",&n);
    memset(sg,-1,sizeof sg); // 用 -1 来表示当前 sg 值没有算过
    for(int i=1;i<=n;++i){
        int a; scanf("%d",&a);
        sum^=get_sg(a);
    }
    if(sum) puts("Yes");
    else puts("No");
    return 0;
}

本文档基于 AcWing算法基础课 制作

视频链接:第四章 数学知识(四) - AcWing

文档版本:

var1.0 完成于2022.02.04.

你可能感兴趣的:(算法竞赛——算法基础课,c++,数学)