什么是尼姆博弈?(最优策略)
尼姆博弈是一个两人博弈,2名玩家轮流从n堆物品中拿取一定数量(最小值1最大值全部)的物品,每次拿取时先选择某一堆,再从中拿取任意数量个物品,至少拿1个,至多将这一堆物品全部拿走,不能不拿。拿到最后一个物品的玩家获胜。
这种题一般不要模拟不要模拟不要模拟(根本模拟不了),所以我们需要在写代码前用数学进行分析和优化。
优化:最优策略是什么
我们采用逆向思维来想n堆沙子的最后的几种情况。
1、当只剩下一堆沙子时: 你的**选择是将所有石子全部拿走,你赢。
2、当剩下两堆沙子时:假设现在有两堆石子且数量不相同,那么你的**选择是取走多的那堆石子中多出来的那几个,使得两堆石子数量相同,这样,不管另一个怎么取,你都可以在另一堆中和他取相同的个数,这样的局面你就是必胜。比如有两堆石子,第一堆有3个,第二堆有5个,这时候你要拿走第二堆的三个,然后两堆就都变成了3个,这时你的对手无论怎么操作,你都可以“学”他,比如他在第一堆拿走两个,你就在第二堆拿走两个,这样你就是稳赢的。同理,两堆沙子数量相同时先手必败。
3、当剩下三堆沙子时 ,我们用(a,b,c)表示3堆沙子的局势,首 先(0,0,0)显然是奇异局势(这里不用管为什么叫奇异局势,往后面看即可)。无论谁面对奇异局势,都必然失败。第二种奇异局势是 (0,n,n),只要与对手拿走一样多的物品,最后都将导致(0,0,0)。仔细分析一下,(1,2,3)也是奇异局势,无论对手如何拿,接下来都可以变为(0,n,n)的情型。
……………………………………………………………………………………………………
博弈双方都采取**策略,那么必然会存在以下的情况
一个状态是必败状态当且仅当它的所有后继都是必胜状态
((先手人的)必胜状态都不可能一步再走到(后手人的)必胜状态)
一个状态是必胜状态当且仅当它至少有一个后继是必败状态
((先手人的)必输状态都可以一步到达(先手人的)必胜状态)
(反证法举例可以证明 以上两个命题的条件或结果一旦更改为假命题)
那么双方的决策就都是给对方留下必败态,那么先手人是否赢(是否给对方留下必败态)只取决于输入堆的分布。
这里我们正式引入尼姆博弈结论(想看怎么来的可以去搜以下数学分析-尼姆博弈)
Bouton定理: 先手能够在非平衡尼姆博弈中取胜,而后手能够在平衡的尼姆博弈中取胜。即状态(x1, x2, x3, …, xn)为必胜状态当且仅当x1 xor x2 xor x3 xor … xor xn =0。这样的操作也称为Nim和(Nim Sum)
而异或运算符号为^;
所以我们只需要堆每堆的沙子数异或即可。
例题1:
例题简要题干:两个玩家A、B,A先拿沙子,每次拿一堆沙子中的若干沙子(最小为1最大为全部),最后一个拿沙子的人赢。
输入 T (1≤T≤10^4)表示数据组数,n堆沙子(10^5)和每堆沙子的个数(10^4)
输出 获胜玩家
代码如下。
#include<iostream> using namespace std; int main() { int T; cin >> T; int n, a; while (T--) { cin >> n; int ans = 0; while(n--) { scanf("%d", &a); ans ^= a; if (ans) printf("A\n"); else printf("B\n"); } } return 0; }
讯享网
例题二(看似尼姆实则贪心)
例题简要题干:两个玩家A和B,A先拿沙子。但是游戏按如下步骤进行:
- 奇数轮由A指定一堆非空的沙子堆开始。然后 B从这堆黑灰中拿走至少一(至多全部)单位数量黑灰。
- 偶数轮由B指定一堆非空的黑灰堆开始。然后A从这堆黑灰中拿走至少一(至多全部)单位数量黑灰。
输入 T (1≤T≤10^4)表示数据组数,n堆沙子(10^5)和每堆沙子的个数(10^4)
输出 获胜玩家
那么双方的决策会受到对方的制约,不符合尼姆博弈的规律,所以本题进行贪心。
再说一次,不要模拟不要模拟不要模拟(根本模拟不了),所以我们需要在写代码前用数学进行分析和优化。
优化一:对输入的数据(堆数)进行优化
对于输入的数据,我们只需要开辟大小为2的int数组,分别储存沙子个数为1的沙堆数和沙子个数大于1的沙堆数。沙子个数大于1的沙堆数可以归并的证明如下。
一堆沙子,其被取走的可能性如下:
1.A取走
2.A取走一部分,B全部取走
3.A取走一部分,B取走一部分,A全部取走(等效为A全部取走)(在最优化策略中是a全部取走,这情况不会出现)
4.……………………(等效为第二步)
所以我们只需要开辟大小为2的int类型num数组,分别计数沙子为1的堆数(num[1})和沙子不为1的堆数num[2],进行判断。
优化二:输入的沙子数为1的堆可以两两相消
因为沙子数为1的沙堆只有一次操作的空间,而最贪心的策略就是模仿对方的下法,所以对方一但指定一个沙子数为1的堆让我全拿,我就同样指定一个一个沙子数为1的堆让对方全拿,即最后沙子数为1的堆只剩1或0。
在这个条件下演化num[2]的个数得到如下结果
所以贪心代码如下
讯享网#include<iostream> using namespace std; int main() { int T; cin >> T; while (T--) { int n,a,sum[3]={0,0,0}; scanf_s("%d", &n); while (n--) { scanf_s("%d", &a); if (a == 1) { sum[1]++; } else { sum[2]++; } } if (sum[2] == 0) { if (sum[1] % 2 == 1) cout << "A" << endl; else cout << "B" << endl; } else { if(sum[1]%2==1) cout << "B" << endl; else cout << "A" << endl; } } return 0; }

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/55564.html