现在的位置: 首页 > 综合 > 正文

[POJ 1704] Georgia and Bob (尼姆博弈变形)

2018年01月12日 ⁄ 综合 ⁄ 共 1818字 ⁄ 字号 评论关闭

POJ 1704 Georgia and Bob  (尼姆博弈变形)

题目链接: http://poj.org/problem?id=1704

题目大意:

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;
}

抱歉!评论已关闭.