第四届蓝桥杯决赛第四题 高僧斗法 (nim博弈 sg函数)深究

高僧斗法

古时丧葬活动中经常请高僧做法事。仪式结束后,有时会有“高僧斗法”的趣味节目,以舒缓压抑的气氛。

节目大略步骤为:先用粮食(一般是稻米)在地上“画”出若干级台阶(表示N级浮屠)。又有若干小和尚随机地“站”在某个台阶上。最高一级台阶必须站人,其它任意。(如图1所示)

两位参加游戏的法师分别指挥某个小和尚向上走任意多级的台阶,但会被站在高级台阶上的小和尚阻挡,不能越过。两个小和尚也不能站在同一台阶,也不能向低级台阶移动。
两法师轮流发出指令,最后所有小和尚必然会都挤在高段台阶,再也不能向上移动。轮到哪个法师指挥时无法继续移动,则游戏结束,该法师认输。
对于已知的台阶数和小和尚的分布位置,请你计算先发指令的法师该如何决策才能保证胜出。
输入数据为一行用空格分开的N个整数,表示小和尚的位置。台阶序号从1算起,所以最后一个小和尚的位置即是台阶的总数。(N<100, 台阶总数<1000)
输出为一行用空格分开的两个整数: A B, 表示把A位置的小和尚移动到B位置。若有多个解,输出A值较小的解,若无解则输出-1。

例如:
用户输入:
1 5 9
则程序输出:
1 4
再如:
用户输入:
1 5 8 10
则程序输出:
1 3
资源约定:
峰值内存消耗 < 64M
CPU消耗 < 1000ms
请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。
所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意: main函数需要返回0
注意: 只使用ANSI C/ANSI C++ 标准,不要调用依赖于编译环境或操作系统的特殊函数。
注意: 所有依赖的函数必须明确地在源文件中 #include , 不能通过工程设置而省略常用头文件。

首先来介绍几个概念:
Nim博弈
遇到P-position必败,当且仅当a1^a2^….^an=0 (此处为位的异或运送) 相同取0,相异取1
何种游戏可以被称为nim博弈:
1、有两名选手;2、两名选手交替对游戏进行移动(move),每次一步,选手可以在(一般而言)有限的合法移动集合中任选一种进行移动;3、对于游戏的任何一种可能的局面,合法的移动集合只取决于这个局面本身,不取决于轮到哪名选手操作、以前的任何操作、骰子的点数或者其它什么因素; 4、如果轮到某名选手移动,且这个局面的合法的移动集合为空(也就是说此时无法进行移动),则这名选手负

nim定理有几个很重要的性质:
定义P-position和N-position,其中P代表Previous,N代表Next。直观的说,上一次move的人有必胜策略的局面是P-position,也就是“后手可保证必胜”或者“先手必败”,现在轮到move的人有必胜策略的局面是N-position,也就是“先手可保证必胜”。更严谨的定义是:1.无法进行任何移动的局面(也就是terminal position)是P-position;2.可以移动到P-position的局面是N-position;3.所有移动都导致N-position的局面是P-position。

如果在博弈过程中,每个局面不可重现,即可对position集合进行拓扑排序。思路分析:递归。
对当前局面,递归计算它的所有子局面的性质,如果存在某个子局面是P-position,那么向这个子局面的移动就是必胜策略!
由于每个局面的子局面都会存在重叠,故可使用dp和记忆化搜索,但过程太过繁琐。
然而非常棒的一个方法就是各位异或为0(虽然我也不知道为什么)

2.被判为N-position的局面一定可以移动到某个p-position
【证明】 若(a1^a2^a3^…..^an)!=0,一定存在着合法的移动,将ai变成aii后满足,a1^a2^..^aii^…^an=0 !!同时这一性质也是解决移动的解法!! 不妨设a1^a2^a3^…..^an=k,则一定存在某个ai它的二进制表示在k的最高位上是1(否则k最高位的1没有k就等于0了!)
这时ai^k

#include<iostream>
#include<stdio.h> 
#include<string>
#define N 100
using namespace std;
int main()
{
    int i, j, k;
    int count=0;

    int monk[N] = { 0 };
    string str;
    getline(cin,str);
    int len = str.length();
    for (j = 0, i = 0; i<len; )
    {
        if (str[i]<'0' || str[i]>'9')
        {
                i++;
        }
        else {
            int n = 0;
            for (; i < len; ++i)
            {
                if (str[i] >= '0'&&str[i] <= '9')
                {
                    n = n * 10 + str[i] - '0';
                }
                else break;
            }
            monk[j++] = n;
        }
    }
    count = j;
    int *a = new int[count];
    for (i = 0, j = 0; j <= (count / 2); j += 2, i++)
    {
        a[i] = monk[j + 1] - monk[j] - 1;
    }
    k = i;
    int sum = a[0];
    for (i = 1; i < k; i++)
    {
        sum ^= a[i];
    }
    if (sum == 0) cout << "-1";
    else {
        for (i = 0; i < k; i++)
        {
            if ((a[i] ^ sum) <= a[i])   j = a[i] ^ sum;
            break;
        }
        cout << monk[2 * i] << " " << monk[2 * i] + a[i] - j;
    }
    delete[]a;
    getchar(); getchar();
    return 0;

}

啊啊啊啊啊啊啊啊啊啊啊啊,我终于写出代码了,其实具备nim博弈的知识下面判断的条件很容易写出,但我就是卡在输入那个地方了,哦吼吼,一会发博文详写。

全部评论

相关推荐

04-25 18:13
五邑大学 Java
无面如何呢:用心包装一下自己的实习
点赞 评论 收藏
分享
04-11 21:31
四川大学 Java
野猪不是猪🐗:(ja)va学弟这招太狠了
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务