You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

6.8 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

[POJ 1704] Georgia and Bob阶梯博弈 题目传送门

一、题目

题目的意思是说:两个人在一个1*N的格子内挪动棋子,刚开始在若干个位置上有若干个棋子,每一个选手可以进行的操作时选择一个棋子并把它向左方移动,不能越过其它的棋子,也不能超出边界。谁不能移动谁就输了。求谁会赢?

中文题意解析

二、解法

0x01 经典Nim游戏解法

我们把棋子按位置升序排列后(因为有可能他给你的就是没有顺序的从后往前把他们两两绑定成一对

如果棋子总个数是奇数,就把最前面一个和边界(位置为0)绑定。 在同一对棋子中,如果对手移动前一个,你总能对后一个移动相同的步数,所以一对棋子的前一个和前一对棋子的后一个之间有多少个空位置对最终的结果是没有影响的。于是我们只需要考虑同一对的两个棋子之间有多少空位

我们把每一对两颗棋子的距离(空位数)视作一堆石子,在对手移动每对两颗棋子中靠右的那一颗时,移动几位就相当于取几个石子,与取石子游戏对应上了,各堆的石子取尽,就相当再也不能移动棋子了。

我们可能还会考虑一种情况,就是某个玩家故意破坏,使得问题无法转换为取石子,例如前一个人将某对中的前者左移,而当前玩家不将这对中的另一端移动,则会导致本堆石子增多了,不符合nim。但是这种情况是不会出现的。因为赢家只要按照取石子进行即可获胜(他要是聪明或者说想赢就绝对不会这么干),而输家无法主动脱离取石子状态。如果输家想要让某堆石子增多,那么赢家只需要让该堆减少回原状,这样输家又要面临跟上一回合同样的局面。

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <vector>
#include <map>
#include <queue>
#include <algorithm>
#include <math.h>
#include <cstdio>

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)

很像阶梯博弈是不是?我们先看下结论,我们把所有奇数堆(从右往左数)的石子数求一下异或和即可。

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <vector>
#include <map>
#include <queue>
#include <algorithm>
#include <math.h>
#include <cstdio>
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;
}
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <vector>
#include <map>
#include <queue>
#include <algorithm>
#include <math.h>
#include <cstdio>
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;
}