n堆硬币,每堆各有xi枚硬币。给定k个数字 a1,a2,a3,…,ak。两人轮流选出一堆硬币,从中取出一些硬币。每次所取硬币枚数一定要在a1,a2,a3,…,ak中。
模板题,贴上java代码
public class Main {
public static void main(String[] args) {
Scanner reader = new Scanner(System.in);
int n = reader.nextInt();
int k = reader.nextInt();
int[] a = new int[k];
int[] x = new int[n];
for (int i = 0; i < k; i++) {
a[i] = reader.nextInt();
}
for (int i = 0; i < n; i++) {
x[i] = reader.nextInt();
}
int max_x = Arrays.stream(x).max().getAsInt();
int[] sg = new int[max_x + 1];
for (int i = 1; i <= max_x; i++) {
Set<Integer> set = new HashSet<>();
for (int j = 0; j < k; j++) {
if (a[j] <= i) {
set.add(sg[i - a[j]]);// 枚举所有可以进行的操作
}
}
int g = 0;
while (set.contains(g)) {
g++;// mex操作,最小非负
}
sg[i] = g;
//System.out.println(g);
}
int ans = 0;
for (int i = 0; i < n; i++) {
ans ^= sg[x[i]];
}
if (ans == 0) {
System.out.println("Bob");
} else {
System.out.println("Alice");
}
}
}
啊好开心,在知乎有关sg函数的回答被大v翻牌了,收获了俩超强大佬的关注,爽飞。但是总感觉自己的理解还是有不对的地方,这里也贴上了留待以后修改吧。
**异或运算是二进制数的运算,若参与异或运算的两个数相同的二进制位的值不同,则该位运算结果为1,否则为0。对于nim游戏进行一整套的异或运算,其实是在判断游戏在开始时是否处于一个所谓的“平衡”局面。若平衡(全部异或下来的值为0),则对于经典的nim游戏胜败判定(无法行动的人负),先手必败,反之,不平衡(大于0)则先手必胜。
“平衡”局面的判定是二进制层面的,这里具体化的来说,就是把nim游戏中的每一堆硬币(或者木块石子什么的)按堆一字排开,再按照二进制的规则摆放,即从右到左按照1,2,4,8….这样的规则放置,比如一堆3个,就从右到左能摆成1,2这样的局面,5则能摆成1,空,4这样。说起来比较晦涩,可以自己摆摆看。对于每一位,如果所有堆这样排开之后,这一位(列)上非空的个数是偶数,则说这一位(列)是平衡的,如果所有位都是平衡的,则说这个nim游戏局面是平衡的。
嗯,这就完成了第一步,把具体的nim游戏转成二进制运算,把堆内的硬币数转成二进制后1就相当于这一位是有所摆放的(不是很会描述这个,5个硬币从右到左摆出来是1,空,4,而5的二进制是101。3摆出来是1,2,而3的二进制是11,言辞浅薄甚是抱歉,请诸位意会)。一次异或运算就是在计算每两堆的局面是否平衡,n次异或运算就是在计算整个局面是否平衡,平衡的局面算出来绝对是0。
至于“平衡”状态与胜败的关系,则是面临平衡局面的人任何操作都会打破平衡,面对不平衡局面的人所能采取的最优操作就是维持平衡,维持到最后,就能主动营造必胜局面。
扩展到sg函数,sg函数是一种把一个大游戏拆成n个符合nim规则的小游戏,连续sg异或就是判断整个大游戏的局面是否平衡,从而得出胜败。**