Georgia和Bob两个人玩游戏,他们设计了如下的的棋盘,有N块格子,从1开始标号。棋盘上不同的格子上有一些棋子,其中每个格子只能有一个棋子。然后他们开始轮流移动棋子,每次选择其中的一个向左移动,每次最少移动一格,最多只能移到前一个的右边。(第一个只能移到1上)。当谁先不能移动棋子便算输。Gerogia先移动。
输入为T组样例,每个样例有n个棋子,位置分别为p1,p2,……pn
这道题困扰了我很久,我都知道他是尼姆博弈了,还是wa成了狗。
我是这么想的,把相邻两堆石头之间的距离当做是一堆石头,每次移动石头,相当于从中取出石子。
但是,后来发现这么想必然是错的。
就如上图所示,把在3的棋子移到了2。那么相当于取走了(1,3)之间的石子,但是(3,6)之间的石子数会变多。很显然,是个错误的假设。
后来还是上网看了别人的思路,才发现这道题的模型建立是那么巧妙。
大致是这样的:
从右往左,两两分为一组,如果棋子数为奇数,则把第一个和墙分为一组。然后把两堆之间的空格数当做是一堆石头。(很神奇,反正我是木有想到。。)
然后证明这样建立模型的正确性。
(1)首先可以保证,每次移动一个棋子,必然是移动某一组(两两分组)的左边或者右边的棋子。
其次是要认识到,对于尼姆博弈,其实每次移动都是在不断变换异或和,即从0到非0再到0的变化过程。(详细解释点击此处)
(2)一组(n,m)如果移动左边的棋子 x 步,一定可以移动对应的右边棋子 x 步,变为(n - x, m - x)。这其实就是从 0 到 非0 再到 0 的过程。
(3)对于(n,m)如果移动右边的棋子 x 步,不能够保证其对应的左边的石子也能够移动 x 步,所以无法采用(2)的方式。但是我们知道,移动了右边的石头,相当于从一堆石头当中取走一些,那么根据尼姆博弈的异或原则,一定可以在另一堆当中取走 y 个石头,使得异或之后又变为0。即移动另一组右边的石头即可。
显然,上述的模型很好的解决了这个问题。但是不知道有没有人思考过为什么一定是从右往左两个分堆呢,如果从左往右分呢,这样子行不行?
我个人尝试了一下,至少我建立的模型是没有很好的解决这个问题。如果说棋子数是偶数个,是一样的。主要是石子数为奇数个时,最后落单的棋子怎么办。我的方法是假想出第 n + 1 个棋子来,位置正好是p[ n ] + 1。这样,相当于补了一堆石子数为 0 的石子进去。然后再用上述的 3 个步骤来操作,感觉上好像是一模一样,但是wa了
我们来分析一下,加进去的石子本身就是虚拟出来的,所以如果移动第 n 颗棋子(对应为左边的),就不能移动其右边的棋子。虽然我们假设出了第 n + 1 堆石子,但这毕竟是我们假设的。如果执迷不悟的想要移动该堆石子,相当于没有操作。
/* 尼姆博弈变形,巧妙的建模思想 */ #include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<map> #include<queue> #include<stack> #include<vector> #include<set> #include<ctype.h> #include<algorithm> #include<string> #define PI acos(-1.0) #define maxn 1000 #define INF 1<<25 #define mem(a, b) memset(a, b, sizeof(a)) typedef long long ll; using namespace std; int p[maxn + 10]; int main () { int t, n, sum; scanf("%d", &t); while(t--) { scanf("%d", &n); sum = 0; for (int i = 0; i < n; i++) scanf("%d", p + i); if (n % 2) p[n++] = 0; //把石子从右向左两两一组,如果为奇数,把位置为0的也当做一枚棋子,但不能移动了 sort(p, p + n); for (int i = 1; i < n; i += 2) sum ^= (p[i] - p[i - 1] - 1); //每一组左右的间隔就是石子数 if (!sum) puts("Bob will win"); else puts("Georgia will win"); } return 0; }