博弈论
Nim游戏:
给定n堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。
问如果两人都采用最优策略.
/*
先手必胜状态:可以走到某一个必败状态(2,2)(0,0)
先手必败状态:走不到任何一个必败状态(1,2)(0,2)
*/
Nim游戏:两人都采取最优策略,谁先遇到相等的状态(0,0)就必输,比如(2,3)先手可以通过制造成(2,2)这样的状态,然后后手在一堆中拿多少,先手就也从另外一堆中拿一样的个数镜像操作,使得后者一定会先遇到(0,0)这样的局面。
必胜状态:用某种方式拿完之后可以让剩下的状态成为先手必败状态。
必败状态:走到所有的状态都是对手先手必胜的状态都是必败状态
如果有N堆石子,a1, a2, a3, ……,an,那么a1 ^ a2 ^ a3 ^ …… ……an = 0,为0的是先手必败状态,不为0的为先手必胜状态。
1)当没有石子时,即已经到达了终点时没有任何操作,0 ^ 0 ^ 0 ^ …… ^ 0 = 0;
2)a1 ^ a2 ^ a3 ^ …… ^ an != 0,通过某种操作,从某一堆石子中拿走若干石子,使剩下的异或为0.
证明过程:a1 ^ a2 ^ a3 ^ …… ^ an = x, 那么与x的最高位1相同的肯定有不少于1位ai(因为假如所有的a在x的最高位没有1个是1的话,那么异或的结果x中这一位肯定是0 ,矛盾),从ai堆我们取走 ai - (ai^x)(ai^x, x最高的左边因为都是0,所以原来ai在这些位中0还是0,1还是1,不会变,但是x是1的最高位这里由1变成了0,变小了,后面的无论怎么变都是小的,所以ai ^ x < ai。那么第ai堆剩下的石子就是 ai = ai - (ai - ai ^ x) = ai ^ x;带入原来的式子
a1 ^ a2 ^ a3 ^ …… ^ ai ^ x ^ ai+1 ^ …… ^ an = x ^ x = 0;
所以如果不等于的时候我必然可以通过取走一些石子使之变成0.
3)a1 ^ a2 ^ a3 ^ …… ^ an = 0, 我能不能通过取走某堆的一些石子使之还是变成0呢?假设ai堆取走之后的剩余石子为aj 那么a1 ^ a2 ^ a3 ^ …… aj ^……an = 0, ai ^ aj = 0, 那么ai = aj.但是我们一定要取石子的,不能不取,所以原来异或是0的局面无法造成异或还是0的局面。矛盾了。
所以异或为0为先手必败局面,异或不为0为先手必胜局面。
那么为什么异或为0是输的呢?因为最终谁先面临全为0的状态是输的,题意中无法操作者视为失败,所以全是0者肯定是输的一方,然后我非0的一方肯定可以使之转化为全0的局面,然后全0的一方也只能转化为非0的局面,这样因为石子是不断被两方取走的,减少的,所以各堆的石子局面一定会变成全是0的状态,而全是0的状态异或为0 必然是上一次异或非0的局面转化而来的。所以综上,异或为0的肯定是先手必输局面。
PS:等式两边异或相同的数字依然是相等的,然后一个数异或另外一个数等于0,那么这另外一个数肯定等于自己。
Nim台阶游戏:
现在,有一个nn级台阶的楼梯,每级台阶上都有若干个石子,其中第ii级台阶上有aiai个石子(i≥1i≥1)。
两位玩家轮流操作,每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶中(不能不拿)。
已经拿到地面上的石子不能再拿,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
输入格式
第一行包含整数nn。
第二行包含nn个整数,其中第ii个整数表示第ii级台阶上的石子数aiai。
输出格式
如果先手方必胜,则输出“Yes”。
否则,输出“No”。
#include <iostream> using namespace std; int main() { int n; cin>>n; int res = 0; for(int i = 1;i<=n;i++) { int a; cin>>a; if(i % 2) res ^= a; } if(!res) cout<<"No"<<endl; else cout<<"Yes"<<endl; }
所有奇数台阶的异或为0时必败,非0时必胜。
如果一开始异或为0,对手从偶数拿下来,那么就把这个拿下来的继续顺拿到下个台阶。奇数异或和为0保持不变。如果从奇数拿下来,同上面经典NIM游戏一样,只能变为异或非0的状态,我一定可以使之再变为为0的状态。
所以我最终一定可以使之变为全部为0 异或也为0的最终目标态的上一步,然后使之变为全0,对手输的局面。
疑点:1、这里即使奇数全为0,偶数台阶还有怎么办?
偶数台阶对手拿下多少?我就从拿到奇数的多少继续拿下去,保持奇数台阶全为0不变,然后这样镜像操作一定可以台阶全为0的状态。
2、为什么不可以按照偶数异或和为0?
因为如果对手处于偶数异或和为0状态,这时候他把第一个台阶的全部拿到了地面之后,那么我们就只能破坏这个偶数异或为0的状态,根据经典Nim游戏,谁破坏了异或为0的的这个局面,就是必败局面。
集合-NIM游戏
#include <cstring> #include <iostream> #include <algorithm> #include <unordered_set> using namespace std; const int N = 110, M = 10010; int n, m; int s[N], f[M]; int sg(int x) { 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() { 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) cout<<"Yes"<<endl; else cout<<"No"<<endl; }
所有起点状态的SG(x)异或起来,如果是0,必败,非0,必胜。
输入:
2
2 5
1
10
输出:
sg[i]:-2 f[i]:0 sg[i]:-5 f[i]:0 sg[i]:0 f[i]:0 sg[i]:-3 f[i]:0 sg[i]:2 f[i]:1 sg[i]:-1 f[i]:0 sg[i]:4 f[i]:0 sg[i]:-1 f[i]:0 sg[i]:-4 f[i]:0 sg[i]:1 f[i]:0 sg[i]:6 f[i]:1 sg[i]:1 f[i]:0 sg[i]:-2 f[i]:0 sg[i]:3 f[i]:1 sg[i]:8 f[i]:0 sg[i]:3 f[i]:1 sg[i]:0 f[i]:0 sg[i]:5 f[i]:2 Yes
疑问:如何通过这两句就达到我们给集合sg元素赋上不属于它后面能够到达的最小自然数的?主要是这递归怎么理解?
拆分Nim游戏:
给定nn堆石子,两位玩家轮流操作,每次操作可以取走其中的一堆石子,然后放入两堆规模更小的石子(新堆规模可以为0,且两个新堆的石子总数可以大于取走的那堆石子数),最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
输入格式
第一行包含整数nn。
第二行包含nn个整数,其中第ii个整数表示第ii堆石子的数量aiai。
输出格式
如果先手方必胜,则输出“Yes”。
否则,输出“No”。
#include <iostream> #include <algorithm> #include <cstring> #include <unordered_set> using namespace std; const int N = 110; int f[N]; int sg(int x) { if(f[x] != -1) return f[x];//记忆化搜索,每个局面sg的值 unordered_set<int> S; //能够到达的两种局面 for(int i = 0; i < x; i ++) for(int j = 0; j <= i; j++) { S.insert(sg(i) ^ sg(j)); } //mex操作 for(int i = 0; ; i++) if(!S.count(i)) return f[x] = i; } int main() { int n; 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"); }