容斥原理 博弈论(多种Nim游戏解法)
目录
- 容斥原理
- 容斥原理的简介
- 能被整除的数(典型例题)
- 实现思路
- 代码实现
- 扩展:用DPS实现
- 博弈论
- 博弈论中的相关性质
- 博弈论的相关结论
- 先手必败必胜的证明
- Nim游戏(典型例题)
- 代码实现
- 台阶-Nim游戏(典型例题)
- 实现思路
- 代码实现
- Mex函数与SG函数
- 集合-Nim游戏(典型例题)
- 代码实现
- 拆分-Nim游戏(典型例题)
- 实现思路
- 代码实现
容斥原理
容斥原理的简介
容斥原理是组合数学中的一个重要原理,讨论了满足某些条件的元素个数问题。
对于 n n n 个集合 A 1 , A 2 , . . . A n A_1,A_2,...A_n A1,A2,...An,定义他们的并集包含的元素个数为 ∣ A 1 ∪ A 2 ∪ . . . ∪ A n ∣ |A_1∪A_2∪...∪A_n| ∣A1∪A2∪...∪An∣,那么根据容斥原理,有:
∣ A 1 ∪ A 2 ∪ ⋯ ∪ A n ∣ |A_1 \cup A_2 \cup \dots \cup A_n| ∣A1∪A2∪⋯∪An∣
= ∣ A 1 ∣ + ∣ A 2 ∣ + ⋯ + ∣ A n ∣ = |A_1| + |A_2| + \dots + |A_n| =∣A1∣+∣A2∣+⋯+∣An∣
− ∣ A 1 ∩ A 2 ∣ − ⋯ − ∣ A n − 1 ∩ A n ∣ - |A_1 \cap A_2| - \dots - |A_{n-1} \cap A_n| −∣A1∩A2∣−⋯−∣An−1∩An∣
+ ∣ A 1 ∩ A 2 ∩ A 3 ∣ + ⋯ + ( − 1 ) n + 1 ∣ A 1 ∩ A 2 ∩ ⋯ ∩ A n ∣ + |A_1 \cap A_2 \cap A_3| + \dots + (-1)^{n+1} |A_1 \cap A_2 \cap \dots \cap A_n| +∣A1∩A2∩A3∣+⋯+(−1)n+1∣A1∩A2∩⋯∩An∣
(奇数项为正 偶数项为负)
例如:

根据上图可得
∣ A ∪ B ∪ C ∣ = ∣ A ∣ + ∣ B ∣ + ∣ C ∣ − ∣ A ∩ B ∣ − ∣ B ∩ C ∣ − ∣ A ∩ C ∣ + ∣ A ∩ B ∩ C ∣ |A \cup B \cup C|= |A|+|B|+|C|-|A \cap B| - |B \cap C| - |A \cap C| + |A \cap B \cap C| ∣A∪B∪C∣=∣A∣+∣B∣+∣C∣−∣A∩B∣−∣B∩C∣−∣A∩C∣+∣A∩B∩C∣
对于 n n n 个集合 A 1 , A 2 , . . . A n A_1,A_2,...A_n A1,A2,...An,其项数可以表示为 C n 1 + C n 2 + ⋯ + C n n C_n^1 + C_n^2+\dots+C_n^n Cn1+Cn2+⋯+Cnn 通过数学计算可得 C n 1 + C n 2 + ⋯ + C n n = 2 n − 1 C_n^1 + C_n^2+\dots+C_n^n = 2^n - 1 Cn1+Cn2+⋯+Cnn=2n−1,用此再乘以每项的时间复杂度便可以得到整体时间复杂度。
另外也可以通过数学计算得到 C n 1 − C n 2 + ⋯ + ( − 1 ) n − 1 C n n = 1 C_n^1 - C_n^2+\dots+(-1)^{n-1}C_n^n = 1 Cn1−Cn2+⋯+(−1)n−1Cnn=1。
能被整除的数(典型例题)
给定一个整数 n n n 和 m m m 个不同的质数 p 1 , p 2 , … , p m p_1,p_2,\dots,p_m p1,p2,…,pm。
请你求出 1 1 1 ~ n n n 中能被 p 1 , p 2 , … , p m p_1,p_2,\dots,p_m p1,p2,…,pm 中的至少一个数整除的整数有多少个。
输入格式:
第一行包含整数 n n n 和 m m m。
第二行包含 m m m 个质数
输出格式:
输出一个整数,表示满足条件的整数的个数。
数据范围:
1 ≤ m ≤ 16 1 \leq m \leq 16 1≤m≤16
1 ≤ n , p i ≤ 1 0 9 1 \leq n,p_i \leq 10^9 1≤n,pi≤109
输入样例:
10 2
2 3
输出样例:
7
实现思路
计算 1 1 1 ~ n n n 中能被 p i p_i pi 一个数整除的整数的方法为 ⌊ n p i ⌋ \huge \lfloor \frac{n}{p_i} \rfloor ⌊pin⌋。
由于不同质数间存在相同的可被整除的整数情况,这时候直接计算就会导致重叠部分计算多次,因此需要将重复的部分剔除出去,这里使用容斥原理来进行剔除操作。
计算 1 1 1 ~ n n n 中能被 p i , p j , p k p_i,p_j,p_k pi,pj,pk 多个数整除的整数(类似于重叠部分)的方法为 ⌊ n p i ∗ p j ∗ p k ⌋ \huge \lfloor \frac{n}{p_i*p_j*p_k} \rfloor ⌊pi∗pj∗pkn⌋。
将 m m m 个质数看作 m m m 个集合, 由于 m m m 较小,我们可以使用int类型的二进制形式(32 bit)的变量来表示对每个集合的选取情况,1代表选用,0代表未选用。
比如:1110 可看作为选用集合 1 2 3,而集合 0 未选用,这样便可以通过一个int类型遍历来表示当前的集合选用情况。
通过二进制数模拟所有集合选用情况,再利用遍历对二进制数进行解码操作,而后进行正负性相加减便可以得到最终结果。
代码实现
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;const int N = 20;
int p[N];int main()
{int n, m, res = 0;cin >> n >> m;for (int i = 0; i < m; ++i) cin >> p[i];// 模拟对所有集合的每一种选用情况for (int i = 1; i < 1 << m; ++i) // 从1开始模拟{int t = 1, cnt = 0;for (int j = 0; j < m; ++j) // 将二进制数所表示的选用情况提取出来(解码){if (!(i >> j & 1)) continue;if ((long long)t * p[j] > n)// 当质数间的乘积大于n时,对应的质数重叠情况只有0个{t = -1;break;}t *= p[j];cnt++;}if (t != -1){int sign = (cnt & 1) ? 1 : -1; // 判断奇偶性res = res + n / t * sign;}}cout << res << endl;return 0;
}
时间复杂度: O ( 2 m ∗ m ) O(2^m*m) O(2m∗m)
通过公式 C n 1 + C n 2 + ⋯ + C n n = 2 n − 1 C_n^1 + C_n^2+\dots+C_n^n = 2^n - 1 Cn1+Cn2+⋯+Cnn=2n−1 可得遍历所有项的时间复杂度为 O ( 2 m ) O(2^m) O(2m), 对单独一项的时间复杂度为 O ( m ) O(m) O(m),这是因为使用二进制数来表示情况,所以需要时间复杂度为 O ( m ) O(m) O(m) 的遍历来进行解码操作,提取其中的选取信息。
扩展:用DPS实现
const int N = 20;
int n, m, p[N], res;// num-代表当前质数乘积
// cur-代表指向质数数组的指针
// cnt-代表所选用集合的总数
void dfs(int num, int cur, int cnt)
{if (cur == m) // 当cur把质数数组都遍历一遍之后代表该种选用情况的结束{if (cnt) // 排除一个质数都不选的选用情况{if (cnt % 2) res += n / num;else res -= n / num;}return;}dfs(num, cur + 1, cnt); // 不选用当前质数的集合dfs(num * p[cur], cur + 1, cnt + 1); // 选用当前质数的集合
}
int main()
{cin >> n >> m;for (int i = 0; i < m; ++i) cin >> p[i];dfs(1, 0, 0);cout << res << endl;return 0;
}
时间复杂度: O ( 2 m ) O(2^m) O(2m)
由于不用二进制数来模拟选取情况,所以也就不需要时间复杂度为 O ( m ) O(m) O(m) 的遍历来从二进制数中提取出选取情况的解码操作。
博弈论
博弈论(Game Theory) 是研究 多方参与者 在某种竞争或合作情况下进行的策略选择和相互作用的一门数学理论。它主要研究参与者如何通过各种策略最大化自己的收益。
博弈论中的相关性质
公平组合游戏(Impartial Combinatorial Game, ICG)是组合博弈论中的一个重要概念。
所谓公平组合游戏,是指具有以下特征:
- 游戏中只有两个玩家,分先手和后手。
- 两者地位完全对等,有同样的可选行动。
- 每个位置要么先手必胜,要么后手必胜,不存在平局。
- 参与者都会采取最优策略。
注:城建棋类游戏,比如围棋,就不是公平组合游戏。因为围棋交战双方分别只能落黑子和白子,胜负判定也比较复杂,不符合公平组合游戏。
一些典型的公平组合游戏包括:
- Nim游戏:从几堆物品中拿走任意数量的游戏。
- 威佐夫游戏:在图上移动棋子吃掉对方的游戏。
- 没有后效应的游戏:当前行动不会对之后可行动产生影响。
通过对这类游戏规律的研究,发展出了一套完整的理论体系,建立了组合博弈论这一独立的学科分支。公平组合游戏抽象化地反映了很多现实世界的竞争情况。
博弈论的相关结论
先手必胜状态:
- 在这个游戏状态下,先手玩家有一个必胜策略,可以通过采取相应的游戏行动最终获胜,不论对手如何行动。
- 即先手玩家可以保证自己赢得游戏。这个游戏状态对先手玩家有利。
先手必败状态:
- 在这个游戏状态下,先手玩家无论采取何种行动最终都会输,后手玩家存在必胜策略。
- 即该状态下无论先手玩家如何行动,后手玩家都可以通过相应的行动获取最终的胜利。这个状态对后手玩家有利。
- 后手玩家可以保证自己赢得游戏。
对于NIM游戏,在两名选手都足够聪明,每一步都是最优解的情况下:
a 1 ⊕ a 2 ⊕ ⋯ ⊕ a n = 0 a_1 \oplus a_2 \oplus \dots \oplus a_n = 0 a1⊕a2⊕⋯⊕an=0 先手必败
a 1 ⊕ a 2 ⊕ ⋯ ⊕ a n ≠ 0 a_1 \oplus a_2 \oplus \dots \oplus a_n \neq 0 a1⊕a2⊕⋯⊕an=0 先手必胜
就是把所有堆的石子个数异或起来,结果是零,先手必败,不是零,先手必胜。
先手必败必胜的证明
①当 a 1 ⊕ a 2 ⊕ ⋯ ⊕ a n = 0 a_1 \oplus a_2 \oplus \dots \oplus a_n = 0 a1⊕a2⊕⋯⊕an=0 时,先手必败:
这个结论实际上是基于一个性质,即对于任意一个非负整数 x x x,有 x ⊕ x = 0 x \oplus x = 0 x⊕x=0。基于这个性质,我们可以证明在这种情况下,先手必败。
假设初始状态下,有 a 1 ⊕ a 2 ⊕ ⋯ ⊕ a n = 0 a_1 \oplus a_2 \oplus \dots \oplus a_n = 0 a1⊕a2⊕⋯⊕an=0。考虑以下情况:
- 如果先手选择 a 1 a_1 a1,那么剩下的异或和就变为 ( a 2 ⊕ ⋯ ⊕ a n ) (a_2 \oplus \dots \oplus a_n) (a2⊕⋯⊕an)。
- 如果先手选择 a 2 a_2 a2,那么剩下的异或和就变为 ( a 1 ⊕ a 3 ⊕ ⋯ ⊕ a n ) (a_1 \oplus a_3 \oplus \dots \oplus a_n) (a1⊕a3⊕⋯⊕an)。
- 以此类推,如果先手选择 a i a_i ai,剩下的异或和就变为 ( a 1 ⊕ ⋯ ⊕ a i − 1 ⊕ a i + 1 ⊕ ⋯ ⊕ a n ) (a_1 \oplus \dots \oplus a_{i-1} \oplus a_{i+1} \oplus \dots \oplus a_n) (a1⊕⋯⊕ai−1⊕ai+1⊕⋯⊕an)。
在这些情况下,无论先手如何选择,剩下的异或和都会变成一个非零值。由于异或满足交换律和结合律,这些选择的结果最终都会导致剩余的异或和为一个非零值。因此,无论先手如何操作,最终的游戏状态都会变为一个不满足条件的状态。
②当 a 1 ⊕ a 2 ⊕ ⋯ ⊕ a n ≠ 0 a_1 \oplus a_2 \oplus \dots \oplus a_n \neq 0 a1⊕a2⊕⋯⊕an=0 时,先手必胜:
在这种情况下,异或和不为零。证明先手必胜需要使用归纳法。对于 n = 1 n=1 n=1的情况,只有一个数,先手直接取走即可。
假设对于 n = k n=k n=k,当 a 1 ⊕ a 2 ⊕ ⋯ ⊕ a k ≠ 0 a_1 \oplus a_2 \oplus \dots \oplus a_k \neq 0 a1⊕a2⊕⋯⊕ak=0 时,先手必胜。现在考虑 n = k + 1 n=k+1 n=k+1 的情况。
将 a 1 ⊕ a 2 ⊕ ⋯ ⊕ a k + 1 a_1 \oplus a_2 \oplus \dots \oplus a_{k+1} a1⊕a2⊕⋯⊕ak+1 分成两部分: x = a 1 ⊕ a 2 ⊕ ⋯ ⊕ a k x = a_1 \oplus a_2 \oplus \dots \oplus a_k x=a1⊕a2⊕⋯⊕ak 和 a k + 1 a_{k+1} ak+1。根据归纳假设,当 x ≠ 0 x \neq 0 x=0 时,先手必胜。此时,先手可以执行以下操作:
- 如果先手选择取走 a k + 1 a_{k+1} ak+1,那么剩下的异或和变为 x x x,根据归纳假设,先手必胜。
- 如果先手选择取走 a i a_i ai( 1 ≤ i ≤ k 1 \leq i \leq k 1≤i≤k),剩下的异或和变为 x ⊕ a i x \oplus a_i x⊕ai。由于 x ≠ 0 x \neq 0 x=0, x ⊕ a i x \oplus a_i x⊕ai 也不为零,那么根据归纳假设,先手也必胜。
因此,无论哪种情况,先手都有必胜策略。综上所述,当 a 1 ⊕ a 2 ⊕ ⋯ ⊕ a n ≠ 0 a_1 \oplus a_2 \oplus \dots \oplus a_n \neq 0 a1⊕a2⊕⋯⊕an=0 时,先手必胜。
Nim游戏(典型例题)
题目描述:
给定 n n n 堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
输入格式:
第一行包含整数 n n n。
第二行包含 n n n 个数字,其中第 i i i 个数字表示第 i i i 堆石子的数量。
输出格式:
如果先手方必胜,则输出 Yes。
否则,输出 No。
数据范围:
1 ≤ n ≤ 1 0 5 , 1 ≤ 1≤n≤10^5,1≤ 1≤n≤105,1≤每堆石子数 ≤ 1 0 9 ≤10^9 ≤109
输入样例:
2
2 3
输出样例:
Yes
代码实现
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;int main()
{int n, res = 0;cin >> n;for (int i = 0; i < n; ++i){int tmp;cin >> tmp;res ^= tmp;}if (res) cout << "Yes" << endl;else cout << "No" << endl;return 0;
}
台阶-Nim游戏(典型例题)
题目描述:
现在,有一个 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 ≤ a i ≤ 1 0 9 1≤n≤10^5,1≤a_i≤10^9 1≤n≤105,1≤ai≤109
输入样例:
3
2 1 3
输出样例:
Yes
实现思路

模拟每次只能向下挪动至下一级台阶的行动,我的想法是将高于台阶1的堆分成多个堆,例如图中台阶5的堆,将这个堆分为5份石子数量相等的台阶1的堆,模拟需要至少5次才能将台阶5的堆拿至地面。
因此可以得到: 2 ⏟ 台阶 1 ⊕ 1 ⊕ 1 ⏟ 台阶 2 ⊕ 3 ⊕ 3 ⊕ 3 ⏟ 台阶 3 ⊕ 2 ⊕ 2 ⊕ 2 ⊕ 2 ⏟ 台阶 4 ⊕ 4 ⊕ 4 ⊕ 4 ⊕ 4 ⊕ 4 ⏟ 台阶 5 \underbrace{2}_{台阶1} \oplus \underbrace{1 \oplus 1}_{台阶2} \oplus \underbrace{3 \oplus 3 \oplus 3}_{台阶3} \oplus \underbrace{2 \oplus 2 \oplus 2 \oplus 2}_{台阶4} \oplus \underbrace{4 \oplus 4 \oplus 4 \oplus 4 \oplus 4}_{台阶5} 台阶1 2⊕台阶2 1⊕1⊕台阶3 3⊕3⊕3⊕台阶4 2⊕2⊕2⊕2⊕台阶5 4⊕4⊕4⊕4⊕4
由于异或(xor)的特性,相同的数异或等于0,便可以进行优化,当阶数为偶数时,异或结果必定为0,奇数时,异或结果直接就等于台阶上的石子数。
从而可以推导出结论:
-
如果 奇数 阶台阶的石子个数 异或值不是零,则 先手必胜。
-
如果 奇数 阶台阶的石子个数 异或值是零, 则 先手必败。
代码实现
未利用异或特性优化前:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;int main()
{int n, res = 0;cin >> n;for (int i = 1; i <= n; ++i){int tmp, cnt = i;cin >> tmp;while (cnt--) res ^= tmp; // 由台阶数分堆}if (res) cout << "Yes" << endl;else cout << "No" << endl;return 0;
}
利用异或特性优化后:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;int main()
{int n, res = 0;cin >> n;for (int i = 1; i <= n; ++i){int tmp;cin >> tmp;if (i & 1) res ^= tmp; // 利用异或特性优化}if (res) cout << "Yes" << endl;else cout << "No" << endl;return 0;
}
Mex函数与SG函数
有向图游戏的概念:
-
节点和边:有向图游戏由一组节点和连接节点的有向边组成。每个节点表示游戏的一个状态,而有向边表示从一个状态到另一个状态的合法转移。
-
玩家轮流移动:在有向图游戏中,玩家轮流进行移动。每一步玩家可以从当前状态转移到某个后继状态,选择的后继状态必须遵循游戏的规则。
-
游戏终止状态:每个有向图游戏都有终止状态,到达终止状态后游戏结束。终止状态可能是获胜状态或失败状态,这取决于游戏的规则。
-
SG 函数和 Mex 函数:SG 函数(Sprague-Grundy 函数)和 Mex 函数是解决有向图游戏的关键工具。SG 函数为每个节点赋予一个非负整数值,表示该节点的状态价值。Mex 函数返回集合中没有出现的最小非负整数。通过 SG 函数和 Mex 函数,可以计算出游戏的最佳策略。
-
必胜策略(先手必胜)和必败策略(先手必败):在有向图游戏中,存在必胜策略和必败策略。如果一个玩家始终能够采取最佳策略,并且可以确保在有限步内获胜,那么该玩家有必胜策略。相反,如果玩家无论如何都无法避免失败,那么该玩家有必败策略。
-
Nim 游戏和其他变体:Nim 游戏是有向图游戏的一个著名例子,它涉及在堆中取石子。还有许多其他变体的有向图游戏,如拈游戏、Grundy’s Game 等。
Mex(Minimum Excluded value)函数,是一个常见的组合游戏和集合论中的概念。它通常用来解决一类博弈问题,其中两个玩家轮流从一个集合中取元素,每次取一个元素,并且不能重复取已经被取走的元素。当某一玩家无法继续操作时,该玩家输掉游戏。
Mex 函数的定义:对于一个给定的非负整数集合,它返回一个非负整数,表示在这个集合中,不属于集合的最小非负整数。
mex ( S ) = min { x ∣ x ∈ N 0 , x ∉ S } \text{mex}(S) = \min \{ x \mid x \in \mathbb{N}_0, x \notin S \} mex(S)=min{x∣x∈N0,x∈/S}
例如:
S = { 0 , 1 , 2 , 4 } ⇒ m e x ( S ) = 3 S=\{0,1,2,4\} ⇒ mex(S)=3 S={0,1,2,4}⇒mex(S)=3
m e x mex mex函数和 s g sg sg函数用于计算有向图游戏的答案,主要思路是:
- m e x mex mex函数返回集合 S S S 中没有出现的最小非负整数。
- s g sg sg函数定义为 s g ( n ) = m e x ( s g ( i 1 ) , s g ( i 2 ) . . . ) sg(n)=mex({sg(i_1),sg(i_2)...}) sg(n)=mex(sg(i1),sg(i2)...),其中 i 1 , i 2 . . . i_1,i_2... i1,i2... 是节点 n n n 的后继节点。
- 对于图 G G G,定义 s g ( G ) = s g ( h e a d ) sg(G)=sg(head) sg(G)=sg(head),其中
head是G的头节点。 - 对于 n n n 个图 G 1 , G 2 , . . . G n G_1,G_2,...G_n G1,G2,...Gn,它们的 s g sg sg函数值的异或和就是这个有向图游戏的答案。
假设有 n n n 个独立局面,它们的 S G SG SG 值分别为 s g ( G 1 ) , s g ( G 2 ) , . . . , s g ( G n ) sg(G1), sg(G2), ..., sg(Gn) sg(G1),sg(G2),...,sg(Gn),其中 G i G_i Gi 表示第 i i i 个局面。那么整个组合游戏的 S G SG SG 值 s g ( G ) sg(G) sg(G) 可以表示为这些局面 S G SG SG 值的异或和:
s g ( G ) = s g ( G 1 ) ⊕ s g ( G 2 ) ⊕ . . . ⊕ s g ( G n ) sg(G) = sg(G_1) \oplus sg(G_2) \oplus ... \oplus sg(G_n) sg(G)=sg(G1)⊕sg(G2)⊕...⊕sg(Gn)
例如:
以NIM游戏为例,数量为7个石子的一堆,每人抽取轮流只能抽取2或5个石子,不能不抽取,判断先手必胜还是先手必败。

从末尾开始计算,可以得到 s g ( h e a d ) = 0 sg(head) = 0 sg(head)=0,由于此时只有一堆,即只存在一个有向图,所以 s g ( h e a d ) = 0 sg(head) = 0 sg(head)=0 得到结果先手必败。
对多个有向图的情况(例如多个堆的情况),将每个有向图的sg(head)异或到一起,再根据先手必胜必败的条件,得出对应的结论。
对 G 1 , G 2 , G 3 , … , G n G_1,G_2,G_3,\dots,G_n G1,G2,G3,…,Gn的 n n n 个有向图,结论为:
先手必败:
s g ( G 1 ) ⊕ s g ( G 2 ) ⊕ s g ( G 3 ) ⊕ ⋯ ⊕ s g ( G n ) = 0 sg(G_1) \oplus sg(G_2) \oplus sg(G_3) \oplus \dots \oplus sg(G_n) = 0 sg(G1)⊕sg(G2)⊕sg(G3)⊕⋯⊕sg(Gn)=0
先手必胜:
s g ( G 1 ) ⊕ s g ( G 2 ) ⊕ s g ( G 3 ) ⊕ ⋯ ⊕ s g ( G n ) ≠ 0 sg(G_1) \oplus sg(G_2) \oplus sg(G_3) \oplus \dots \oplus sg(G_n) \neq 0 sg(G1)⊕sg(G2)⊕sg(G3)⊕⋯⊕sg(Gn)=0
集合-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 ≤ s i , h i ≤ 10000 1≤n,k≤100,1≤s_i,h_i≤10000 1≤n,k≤100,1≤si,hi≤10000
输入样例:
2
2 3
输出样例:
Yes
代码实现
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstring>
#include<unordered_set>
using namespace std;const int N = 110, M = 10010;
int a[N], f[M], k, n;int sg(int x)
{if (~f[x]) return f[x]; // 记忆化搜索:保证每种状态只会被算一次unordered_set<int> s; // 用哈希表存储当前节点的集合所存有的非负数的元素for (int i = 0; i < k; ++i){int num = a[i];if (x >= num) s.insert(sg(x - num)); // 将新的状态加进来}for (int i = 0;; i++) if (!s.count(i)) return f[x] = i; // 找出不存在集合当中的最小值并返回
}
int main()
{int res = 0;memset(f, -1, sizeof f);cin >> k;for (int i = 0; i < k; ++i) cin >> a[i];cin >> n;for (int i = 0; i < n; ++i){int x;cin >> x;res ^= sg(x); // 求出每堆石子的SG值将其异或}if (res) cout << "Yes" << endl;else cout << "No" << endl;return 0;
}
拆分-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 1≤n,ai≤100
输入样例:
2
2 3
输出样例:
Yes
实现思路
取走其中的一堆石子,然后放入两堆 规模更小 的石子,相当于将一个局面拆分成了两个局面,由SG函数理论:多个独立局面的SG值,等于这些局面SG值的 异或和。
s g ( G ) = s g ( G 1 ) ⊕ s g ( G 2 ) ⊕ . . . ⊕ s g ( G n ) sg(G) = sg(G_1) \oplus sg(G_2) \oplus ... \oplus sg(G_n) sg(G)=sg(G1)⊕sg(G2)⊕...⊕sg(Gn)
代码实现
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstring>
#include<unordered_set>
using namespace std;const int N = 110, M = 10010;
int f[N], n;int sg(int x)
{if (~f[x]) 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()
{memset(f, -1, sizeof f);cin >> n;int res = 0;while (n--){int x;cin >> x;res ^= sg(x);}if (res) cout << "Yes" << endl;else cout << "No" << endl;return 0;
}
相关文章:
容斥原理 博弈论(多种Nim游戏解法)
目录 容斥原理容斥原理的简介能被整除的数(典型例题)实现思路代码实现扩展:用DPS实现 博弈论博弈论中的相关性质博弈论的相关结论先手必败必胜的证明Nim游戏(典型例题)代码实现 台阶-Nim游戏(典型例题&…...
【C++】函数指针
2023年8月18日,周五上午 今天在B站看Qt教学视频的时候遇到了 目录 语法和typedef或using结合我的总结 语法 返回类型 (*指针变量名)(参数列表)以下是一些示例来说明如何声明不同类型的函数指针: 声明一个不接受任何参数且返回void的函数指针…...
VBA技术资料MF45:VBA_在Excel中自定义行高
【分享成果,随喜正能量】可以不光芒万丈,但不要停止发光。有的人陷入困境,不是被人所困,而是自己束缚自己,这时"解铃还须系铃人",如果自己无法放下,如何能脱困? 。 我给V…...
【Git】Git中的钩子
Git Book——Git的自定义钩子 Git中的钩子分为两大类: 1、客户端钩子:由诸如提交和合并这样的操作所调用 2、服务端钩子:由诸如接收被推送的提交这样的联网操作 客户端钩子: 提交工作流钩子 pre-commit:在提交信息前…...
java 工程管理系统源码+项目说明+功能描述+前后端分离 + 二次开发 em
Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下: 首页 工作台:待办工作、消息通知、预警信息,点击可进入相应的列表 项目进度图表:选择(总体或单个)项目显…...
Java # JVM
一、1.8之前 运行时数据区(进程共享) 运行时常量池为什么要有方法区: jvm完成类装载后,需要将class文件中的常量池转入内存,保存在方法区中为什么是常量: 常量对象操作较多,为了避免频繁创建和…...
vscode远程连接Linux失败,提示过程试图写入的管道不存在(三种解决办法)
vscode报错如下: 一、第一种情况 原因是本地的known_hosts文件记录服务器信息与现服务器的信息冲突了,导致连接失败。 解决方案就是把本地的known_hosts的原服务器信息全部删掉,然后重新连接。 二、第二种情况 在编写配置文件config时&…...
elaticsearch(1)
1.简介 Elasticsearch是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。 Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引…...
使用pnpm workspace管理Monorepo架构
在开发项目的过程中,我们需要在一个仓库中管理多个项目,每个项目有独立的依赖、脚手架,这种形式的项目结构我们称之为Monorepo,pnpm workspace就是管理这类项目的方案之一。 一、pnpm简介 1、pnpm概述 pnpm代表performance npm…...
Ubuntu16.04-ros-kinetic环境搭建笔记=1=
tips:搬运资料,留个记录 安装Ubuntu Ubuntu官网下载地址 安装 虚拟机安装Ubuntu 最好断网安装Ubuntu,可以节约时间 Ubuntu基础设置 Ubuntu换国内源 换成清华源 sudo apt upgradeVMwareTool安装 把这个压缩包拖到桌面,否则只读…...
应用层自定义协议(组织数据的格式)
概念 在进行网络传输数据的时候,通常是将要传输的数据组织成一个字符串,再将字符串转换为一个字节流进行网络传输数据,而数据组织的格式是多种多样的,我们只需要保证,客户端和服务器对于字符串的组织和解析统一即可 现…...
5种常见的3D游戏艺术风格及工具栈
在游戏开发领域,3D 艺术风格已成为为玩家创造身临其境、引人入胜的体验的重要组成部分。 随着技术的进步,创造令人惊叹的 3D 视觉效果的可能性已经大大扩展,为游戏开发人员提供了广泛的选择。 在本文中,我们将探讨当今游戏开发中…...
【玩转Linux操作】crond的基本操作
🎊专栏【玩转Linux操作】 🍔喜欢的诗句:更喜岷山千里雪 三军过后尽开颜。 🎆音乐分享【Counting Stars 】 欢迎并且感谢大家指出小吉的问题🥰 文章目录 🍔概述🍔命令⭐常用选项 🍔练…...
设置Linux 静态IP
LInux虚拟机默认的IP地址是动态获取的 作为服务器,我们一般还需要把IP地址设置为静态的 设置静态IP vim /etc/sysconfig/network-scripts/ifcfg-ens33 TYPEEthernet PROXY_METHODnone BROWSER_ONLYno # BOOTPROTOdhcp 动态获取 BOOTPROTOstatic IPADDR"192.16…...
JMeter接口自动化测试实例—JMeter引用javaScript
Jmeter提供了JSR223 PreProcessor前置处理器,通过该工具融合了Java 8 Nashorn 脚本引擎,可以执行js脚本以便对脚本进行前置处理。其中比较典型的应用就是通过执行js脚本对前端数据进行rsa加密,如登录密码加密。但在这里我就简单的应用javaScr…...
javascript期末作业【三维房屋设计】 【源码+文档下载】
1、引入three.js库 官网下载three.js 库 放置目录并引用 引入js文件: 设置场景(scene) (1)创建场景对象 (2)设置透明相机 1,透明相机的优点 透明相机机制更符合于人的视角,在场景预览和游戏场景多有使用…...
数组详解
1. 一维数组的创建和初始化 1.1 数组的创建 数组是一组相同类型元素的集合。 数组的创建方式: type_t arr_name [const_n]; //type_t 是指数组的元素类型 //const_n 是一个常量表达式,用来指定数组的大小 数组创建的实例: //代码1 int a…...
【记录COCO数据集格式】实例分割的annotations.json的内部格式
在此记录一下实例分割coco的annotations.json的格式 annotations.json,整体是一个字典: { "info": {"description": null,"url": null, "version": null, "year": 2023, "contributor": null, "date_created…...
mac 关于获取手机信息 终端指令
iOS真机命令(自动化测试) 获取设备的的UDID idevice_id --list # 显示当前所连接设备的 udid instruments -s devices # 列出所有设备,包括真机、模拟器、mac ideviceinfo 可以在返回的数据中找到 udid idevice_id -l 苹果手机 safari打开网…...
ios消息推送例子
通过Apple推送服务,将消息发送给特定的ios客户端,这是服务器端实例代码。需要客户端的voip key值,以及相应的客户端回调接口,支持ios9.0以上版本。 下载地址:https://download.csdn.net/download/m0_37567738/8821559…...
RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...
2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
