如无特殊说明,所有数均为正整数.
例如下维恩图:
如果想要求出左图三个圆覆盖的面积时,设黑边圆的面积为 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=Sk∩Sr,黑边圆与蓝边圆共同覆盖的面积为 S k , b = S k ∩ S b S_{k,b}=S_k\cap S_b Sk,b=Sk∩Sb,红边圆与蓝边圆共同覆盖的面积为 S r , b = S r ∩ S b S_{r,b}=S_r\cap S_b Sr,b=Sr∩Sb.
如右图所示,设三个圆共同覆盖的面积为 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=Sk∩Sr∩Sb.
故三个圆覆盖的面积为:
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+Sb−Sk,r−Sk,b−Sr,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+Sb−Sk,r−Sk,b−Sr,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+Sb−Sk,r−Sk,b−Sr,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+S2−S1∩S2;
如果是四个圆 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+S4−S1∩S2−S1∩S3−S1∩S4−S2∩S3−S2∩S4−S3∩S4+S1∩S2∩S3+S1∩S2∩S4+S1∩S3∩S4+S2∩S3∩S4−S1∩S2∩S3∩S4
则对于 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=S1个圆−S2个圆+S3个圆+⋯+(−1)n−1Sn个圆
其中 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)=2n−1 项.
- 可以看作从 n n n 个数中选取任意多个数的方案数,每个数都有选与不选两种情况,总共为 2 n 2^n 2n 种方案,去掉什么数都不选的那种,即为 2 n − 1 2^n-1 2n−1 项;
- 也可以设 ( 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)n−1 呢?
- 证明
- 对于 ∣ ⋃ 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∣=∑iSi−∑i,j∣Si∩Sj∣+∑i,j,k∣Si∩Sj∩Sk∣+⋯,若等式左边的任何一个元素 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(1⩽k⩽n) 次,则其总共计算了 ( 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)k−1(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)k−1(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)k−1(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)k−1(kk)=1.
- ( − 1 ) k + 1 = ( − 1 ) k − 1 (-1)^{k+1}=(-1)^{k-1} (−1)k+1=(−1)k−1.
- 所以可以得证 n n n 个圆的面积只被计算了一次.
- 证毕
算法题中涉及到的算法知识绝大多数都是离散数学中的内容.
原题链接
给定一个整数 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 1∼n 中能被 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 1≤m≤16,
1 ≤ n , p i ≤ 1 0 9 1≤n,p_i≤10^9 1≤n,pi≤109
输入样例:
10 2
2 3
输出样例:
7
时/空限制: 1s / 64MB
来源: 模板题
算法标签:容斥原理
样例中,在 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 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 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 ∣S2∪S3∣=∣S2∣+∣S3∣−∣S2∩S3∣=5+3−1=7.
#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 A1xorA2xor⋯xorAn=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 A1xorA2xor⋯xorAn=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 A1xorA2xor⋯xorAn=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 A1xorA2xor⋯xorAn=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 A1xorA2xor⋯xorAixor⋯xorAn=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 A1xorA2xor⋯xorAn=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 A1xorA2xor⋯xorAn=0 为必败局面,无论如何行动,都会让对手面临一个 “各堆石子异或起来不等于 0 0 0” 的必胜局面.
证毕
可以了解一下巴什博弈,推荐博客:简单易懂的博弈论讲解(巴什博弈、尼姆博弈、威佐夫博弈、斐波那契博弈、SG定理) - The_Virtuoso - 博客园 (cnblogs.com)
公平组合游戏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 ∈ N , x ∉ S { x } mex(S)=min_{x\in N,x \notin S}\{x\} mex(S)=minx∈N,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)xor⋯xorSG(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)xor⋯xorSG(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)xor⋯xorSG(Gm)=x=0,则会存在 S G ( x i ) x o r x < S G ( x i ) SG(x_i) \;xor\;x
SG(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)xor⋯xorSG(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)xor⋯xorSG(Gm)=0,利用反证法,通过消去律得到 S G ( x i ) = k → S G ( x i ) ′ = k SG(x_i)=k \to SG(x_i)'=k SG(xi)=k→SG(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)xor⋯xorSG(Gm)=0.
原题链接
给定 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 1≤n≤105,
1 ≤ 1≤ 1≤每堆石子数 ≤ 1 0 9 ≤10^9 ≤109
输入样例:
2
2 3
输出样例:
Yes
时/空限制: 1s / 64MB
来源: 模板题
算法标签:数学知识
博弈论
Nim游戏
例如有两堆石子 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 A1xorA2xor⋯xorAn=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;
}
原题链接
现在,有一个 n n n 级台阶的楼梯,每级台阶上都有若干个石子,其中第 i i i 级台阶上有 a i a_i ai 个石子( i ≥ 1 i≥1 i≥1)。
两位玩家轮流操作,每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶中(不能不拿)。
已经拿到地面上的石子不能再拿,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
输入格式
第一行包含整数 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 1≤n≤105,
1 ≤ a i ≤ 1 0 9 1≤a_i≤10^9 1≤ai≤109
输入样例:
3
2 1 3
输出样例:
Yes
时/空限制: 1s / 64MB
来源: 模板题
算法标签:数学知识
博弈论
Nim游戏
对于样例:
图中红色操作为先手操作,橙色操作为后手操作,则:
样例操作图示
- 对于先手来说,只需要让 1 1 1 号台阶与 3 3 3 号台阶的石子数量保持相等即可,例如先手的第一次操作,是从 3 3 3 号台阶拿出 1 1 1 个石子放到 2 2 2 号台阶上,此时:
- 若后手从 2 2 2 号台阶上拿取 x x x 个石子放到 1 1 1 号台阶上,先手可以把这 x x x 个石子放到地面上,此时仍有 1 1 1 号台阶的石子数量等于 3 3 3 号台阶的石子数量;
- 若后手从 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 号台阶,先手就可以搬多少到地面,后手显然必输.
#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;
}
原题链接
给定 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 1≤n,k≤100,
1 ≤ s i , h i ≤ 10000 1≤s_i,h_i≤10000 1≤si,hi≤10000
输入样例:
2
2 5
3
2 4 7
输出样例:
Yes
时/空限制: 1s / 64MB
来源: 模板题
算法标签:数学知识
博弈论
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;
}
原题链接
给定 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 1≤n,ai≤100
输入样例:
2
2 3
输出样例:
Yes
时/空限制: 1s / 64MB
来源: 模板题
算法标签:数学知识
博弈论
SG函数
即对于大小为 x x x 的某个石子堆,最坏情况即为变成两堆大小为 x − 1 x-1 x−1 石子堆,这样对此堆的操作过程就犹如一棵满二叉树,显然其操作次数为 2 x − 1 2^{x}-1 2x−1;
即对于大小为 x x x 的某个石子堆,它及经它操作后得到的石子堆,最多共计能进行 2 x − 1 2^{x}-1 2x−1 次操作.
对于某个大小为 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.