Problem Description:
Alice 和 Bob 在玩一个游戏。给定 k 个数字 a1,a2,……,ak。一开始,有n堆硬币,每堆各有 Xi 枚硬币。Alice 和 Bob 轮流选出一堆硬币,从中取出一些硬币。每次所选硬币的枚数一定要在 a1,a2,……,ak 当中。Alice先取,取光硬币的一方获胜。当双方都采取最优策略时,谁会获胜?题目保证a1,a2……中一定有1.
1<=n<=1000000
1<=k<=100
1<=Xi,ai<=10000
Input:
n=3
k=3
a={1,3,4}
x={5 ,6,7}
Output:
Alice
这和4.1.1中介绍的硬币问题类似,但那道题中只有一堆硬币,而本题中有n堆。如果依然用动态规划算法的话,状态数将高达O(X1*X2*……*Xn)。
为了更高效地求解这个问题,要了解一下Grundy值这一重要概念。利用它,不光是这个游戏,其他许多游戏都可以转换成前面所介绍的Nim。
让我们再来考虑一下只有一堆硬币的情况。硬币枚数所对应的Grundy值的计算方法如下。
int grundy(int x){ S={}; for(i=1,……,k){ if(a_i<=x) //将Grundy(x-a_i)加到S中 } return //最小的不属于S的非负整数 }
也就是说,当前状态的Grundy值就是除任意一步所能转移到的状态的Grundy值以外的最小非负整数。这样的Grundy值,和Nim中的一个石子堆类似,有如下性质。
Nim中有x颗石子的石子堆,能够转移成0,1,……,x-1颗石子的石子堆;
从Grundy值为x的状态出发,可以转移到Grundy值为0,1,……,x-1的状态;
只不过,与Nim不同的是转移后的Grundy值也有可能增加。不过,对手总能选取合适的策略再转移回相同Grundy值的状态,所以对胜负没有影响。(但是,对于状态可能有循环时,需要注意不分胜负·达成平局(游戏不会结束)的情况。因为在这个游戏中,石子数始终是减少的,所以不会发生平局)
另外,上面的程序是用单纯的递归函数实现的,改成动态规划或记忆化搜索之后,就能够保证求解的复杂度为O(xk)。
了解了一堆硬币的Grundy值的计算方法之后,就可以将它看作Nim中的一个石子堆。Nim中为什么用如下方法判断胜负。
所有石子堆的石子数Xi的XOR
X1 XOR X2 XOR …… XOR Xk
为零则必败,否则必胜
Grundy值等价于Nim中的石子数,所以对于Grundy值的情况,有
所有硬币堆的Grundy值的XOR
grundy(X1) XOR grundy(X2) XOR ……XOR grundy(Xk)
为零则必败,否则必胜
不光是这个游戏,在许多游戏中,都可以根据“当前状态的Grundy值等于除任意一步所能转移到的状态的Grundy值以外的最小非负整数”这一性质,来计算Grundy值,再根据XOR来判断胜负。
//输入 int N,K,X[MAX_N],A[MAX_K]; //利用动态规划计算Grundy值的数组 int grundy[MAX_N+1]; void solve(){ //轮到自己时剩0枚则必败 grundy[0]=0; //计算grundy值 int max_x= *max_element(X,X+N); for(int j=1;j<max_x;j++){ set<int> s; for(int i=0;i<K;i++) if(A[i]<=j) s.insert(grundy[j-A[i]]); int g=0; while(s.count(g)!=0) g++; grund[j]=g; } //判断胜负 int x=0; for(int i=0;i<N;i++) x^=grundy[x[i]]; if(x) puts("Alice"); else puts("Bob"); }
原文地址:https://www.cnblogs.com/astonc/p/9954838.html