经典Nim和台阶Nim打卡活动中已写过,这篇分享通过一道题目增强对Nim的理解
参考博客:蓝桥杯第四届国赛 高僧斗法
一、分析
总结:两两分组,第1和第2个人一组,第3和第4个人一组......分完组后,Nim堆是每个组之间的台阶数目,如果各组之间的台阶数目异或起来结果非0,先手必胜,结果是0,先手必败,输出-1。
为啥是这样的呢??
如果当前状态是“各组之间的台阶数目异或起来结果非0”,根据经典Nim结论可得,先手一定可以通过移动一个人的位置,使状态达到“各组之间的台阶数目异或起来结果是0”。
(这里需要解释一下:如果先手移动处于偶数位置的人的位置,那么后手可以移动跟他同组的处于奇数位置的人的位置,使异或结果不发生变化,异或结果仍是非0;如果先手移动处于奇数位置的人的位置,这是才会对异或的结果产生影响,这也是这么分组的原因)
接着,后手拿到的是异或为0的状态,再往后,后手一定会改变这种状态,达到“异或结果非0”的状态,并把此状态抛给先手,依次循环,直至终止状态,所有人都会挤在高段台阶。
二、代码实现
1.如果当前状态是“各组之间的台阶数目异或起来结果是0”,必败,直接输出-1,return。
2.如果当前状态是“各组之间的台阶数目异或起来结果非0”,必胜,怎么寻找必胜的第一步策略呢??
暴力遍历:将当前位置的人依次移动,直至到下一人的位置,其中的位置如果满足“各组之间的台阶数目异或起来结果是0”,停止,将此状态抛给后手,后手必败,我必胜,输出此时的策略。
三、代码
package jingma;
import java.util.Scanner;
public class T7510 {
static Scanner in=new Scanner(System.in);
static String[] s=in.nextLine().split(" ");
static int len=s.length;
static int[] arr=new int[len];
public static void main(String[] args) {
for (int i = 0; i < len; i++) {
arr[i]=Integer.parseInt(s[i]);
}
// 如果当前状态是“各组之间的台阶数目异或起来结果是0”,必败
if (cal()==0) {
System.out.println("-1");
return;
}
// 否则,寻找必胜策略:遍历
for (int i = 0; i <len-1; i++) {
int t=arr[i];
// 将当前位置的人依次移动,最多移动到下一人的位置
for (int j = arr[i]+1; j < arr[i+1]; j++) {
arr[i]=j;
// 当前位置满足“各组之间的台阶数目异或起来结果是0”,停止,将此状态抛给后手,后手必败,我必胜,输出此时的策略
if (cal()==0) {
System.out.println(t+" "+arr[i]);
return;
}
arr[i]=t;
}
}
}
// 计算各组直接台阶数目的异或结果
private static int cal() {
int res=0;
for(int i=0;i<len-1;i+=2) {
res^=(arr[i+1]-arr[i]-1);
}
return res;
}
}