[POJ 1704] Georgia and Bob(阶梯博弈) [题目传送门](http://poj.org/problem?id=1704) ### 一、题目 题目的意思是说:两个人在一个$1*N$的格子内挪动棋子,刚开始在若干个位置上有若干个棋子,每一个选手可以进行的操作时**选择一个棋子并把它向左方移动**,不能越过其它的棋子,也不能超出边界。谁不能移动谁就输了。求谁会赢? [中文题意解析](https://blog.csdn.net/weixin_43311695/article/details/104570854)
### 二、解法 #### 0x01 经典$Nim$游戏解法 我们把棋子按位置升序排列后(**因为有可能他给你的就是没有顺序的**),**从后往前把他们两两绑定成一对**。 如果棋子总个数是奇数,就把最前面一个和边界(位置为$0$)绑定。 在同一对棋子中,如果对手移动前一个,你总能对后一个移动相同的步数,所以一对棋子的前一个和前一对棋子的后一个之间有多少个空位置对最终的结果是没有影响的。于是我们只需要**考虑同一对的两个棋子之间有多少空位**。 我们把每一对两颗棋子的距离(**空位数**)视作一堆石子,在对手移动每对两颗棋子中**靠右**的那一颗时,移动几位就相当于**取几个石子**,与取石子游戏对应上了,各堆的石子取尽,就相当再也不能移动棋子了。 我们可能还会考虑一种情况,就是某个玩家故意破坏,使得问题无法转换为取石子,例如前一个人将某对中的**前者左移**,而当前玩家不将这对中的另一端移动,则会导致本堆石子增多了,不符合$nim$。但是这种情况是不会出现的。因为赢家只要按照取石子进行即可获胜(他要是聪明或者说想赢就绝对不会这么干),而输家无法主动脱离取石子状态。如果输家想要让某堆石子增多,那么赢家只需要让该堆减少回原状,这样输家又要面临跟上一回合同样的局面。 ```c++ #include #include #include #include #include #include #include #include #include const int N = 1010; using namespace std; int T, a[N]; int main() { scanf("%d", &T); while (T--) { int n, res = 0; scanf("%d", &n); //要明确在a[i]中装的内容是放置棋子的格式号! for (int i = 1; i <= n; i++) scanf("%d", &a[i]); //把棋子按位置升序排列(因为有可能他给你的就是没有顺序的) sort(a + 1, a + n + 1); //从后向前,两两一组 for (int i = n; i > 0; i = i - 2) res ^= (a[i] - a[i - 1] - 1); // a[i] - a[i - 1] - 1 表示两个棋子之间的空格数量,理解为石子数 if (res) puts("Georgia will win"); //先手必胜 else puts("Bob will win"); //先手必败 } return 0; } ``` #### 0x02 台阶$Nim$游戏解法 例如已知$8$颗棋子的位置$1, 5, 6, 7, 9, 12, 14, 17$,每颗棋子向左的最大移动步数为$0,3,0,0,1,2,1,2$ 我们将第$2$个棋子**向左**移动一步,则局面变为$0,2,1,0,1,2,1,2$,发现它右边第一个棋子的限制边宽松了。 我们向**台阶$Nim$游戏**上想,相当于从第二堆取走$1$颗石子放到第三堆里。 **模型简化**:$n$个石子堆,每次操作可以取第$i$堆石子若干放到第$i+1$堆,或者从第$n$堆里直接取走若干。(**反着的台阶$Nim$**) **很像阶梯博弈是不是**?我们先看下结论,我们把所有奇数堆(**从右往左数**)的石子数求一下**异或和**即可。
```c++ #include #include #include #include #include #include #include #include #include using namespace std; // 8个数字的用例 /* 测试用例: 1 8 1 5 6 7 9 12 14 17 输出: i=1,a[8]-a[7]-1=2 i=3,a[6]-a[5]-1=2 i=5,a[4]-a[3]-1=0 i=7,a[2]-a[1]-1=3 Georgia will win */ const int N = 1010; int a[N]; int main() { int T; scanf("%d", &T); while (T--) { int n, s = 0; scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); sort(a + 1, a + n + 1); /* n=8 a[1~8] = {1 5 6 7 9 12 14 17} a[8]-a[7]-1=17-14-1=2 1号台阶 √ a[7]-a[6]-1=14-12-1=1 2号台阶 × a[6]-a[5]-1=12- 9-1=2 3号台阶 √ a[5]-a[4]-1=9-7-1 =1 4号台阶 × a[4]-a[3]-1=7-6-1 =0 5号台阶 √ a[3]-a[2]-1=6-5-1 =0 6号台阶 × a[2]-a[1]-1=5-1-1 =3 7号台阶 √ 需要计算 1号台阶 3号台阶 5号台阶 7号台阶 2 ∧ 2 ∧ 0 ∧ 3 */ for (int i = 1; i <= n; i += 2) //枚举 奇数阶台阶 { s ^= a[n - i + 1] - a[n - i] - 1; printf("i=%d,a[%d]-a[%d]-1=%d\n", i, n - i + 1, n - i, a[n - i + 1] - a[n - i] - 1); } //异或和为0,先手必败;否则先手必胜 puts(s == 0 ? "Bob will win" : "Georgia will win"); } return 0; } ``` ```c++ #include #include #include #include #include #include #include #include #include using namespace std; const int N = 1010; int a[N]; /* 测试用例: 1 7 5 6 7 9 12 14 17 输出: i=1,a[7]-a[6]-1=2 i=3,a[5]-a[4]-1=2 i=5,a[3]-a[2]-1=0 i=7,a[1]-a[0]-1=4 Georgia will win */ int main() { int T; scanf("%d", &T); while (T--) { int n, s = 0; scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); sort(a + 1, a + n + 1); /* n=7 a[1~7] = {5 6 7 9 12 14 17} a[7]-a[6]-1=2 1号台阶 √ a[6]-a[5]-1=1 2号台阶 × a[5]-a[4]-1=2 3号台阶 √ a[4]-a[3]-1=1 4号台阶 × a[3]-a[2]-1=0 5号台阶 √ a[2]-a[1]-1=0 6号台阶 × a[1]-a[0]-1=0 7号台阶 √ */ for (int i = 1; i <= n; i += 2) //枚举 奇数阶台阶 { s ^= a[n - i + 1] - a[n - i] - 1; printf("i=%d,a[%d]-a[%d]-1=%d\n", i, n - i + 1, n - i, a[n - i + 1] - a[n - i] - 1); } //异或和为0,先手必败;否则先手必胜 puts(s == 0 ? "Bob will win" : "Georgia will win"); } return 0; } ```