• POJ1704 Georgia and Bob(阶梯博弈)


    [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;
    }
    
  • 相关阅读:
    Zend Studio下调试PHP的一点注意事项
    使用FTP出现中文乱码解决方案
    C++数据类型与C#对应表
    FineUI 开发B/S系统
    c#调用带输出参数的存储过程
    经典FormsAuthenticationTicket 分析
    C# Oracle数据库操作类实例详解
    如何使用C#的Hashtable[哈希表]
    对三层架构的理解
    C# 最简单的三层架构实例 ——转载自网易博客
  • 原文地址:https://www.cnblogs.com/littlehb/p/16404976.html
Copyright © 2020-2023  润新知