蓝桥杯 试题 历届试题 高僧斗法 博弈论

问题描述
  古时丧葬活动中经常请高僧做法事。仪式结束后,有时会有“高僧斗法”的趣味节目,以舒缓压抑的气氛。
  节目大略步骤为:先用粮食(一般是稻米)在地上“画”出若干级台阶(表示N级浮屠)。又有若干小和尚随机地“站”在某个台阶上。最高一级台阶必须站人,其它任意。(如图1所示)
  两位参加游戏的法师分别指挥某个小和尚向上走任意多级的台阶,但会被站在高级台阶上的小和尚阻挡,不能越过。两个小和尚也不能站在同一台阶,也不能向低级台阶移动。
  两法师轮流发出指令,最后所有小和尚必然会都挤在高段台阶,再也不能向上移动。轮到哪个法师指挥时无法继续移动,则游戏结束,该法师认输。
  对于已知的台阶数和小和尚的分布位置,请你计算先发指令的法师该如何决策才能保证胜出。
输入格式
  输入数据为一行用空格分开的N个整数,表示小和尚的位置。台阶序号从1算起,所以最后一个小和尚的位置即是台阶的总数。(N<100, 台阶总数<1000)
输出格式
  输出为一行用空格分开的两个整数: A B, 表示把A位置的小和尚移动到B位置。若有多个解,输出A值较小的解,若无解则输出-1。
样例输入
1 5 9
样例输出
1 4
样例输入
1 5 8 10
样例输出
1 3
解题思路:nim定理+如何应用在本题
nim定理证明:(参考https://blog.csdn.net/qq_39861441/article/details/82936117 )在这里我用编程的思维解释证明过程
 
定义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。
按照这个定义,如果局面不可能重现,或者说positions的集合可以进行拓扑排序,那么每个position或者是P-position或者是N-position,而且可以通过定义计算出来。
编程角度:关于博弈问题我们经常使用的方式是递归调用,每次改变当前局面再交给对方处理。如果所有可能走法有一步返回胜(对方返回负)可以取胜,否则负。
 f(局面 x)
{
    边界处理
 
    for( 我可能走的所有走法 ){
        试着走一步---->局面y
        胜负 = f(y)
        if( f(y)==负 ) return 胜
        恢复局面(或者直接传入改变的变量)
    }
    return 负  
}
结合上定义,P-position即函数返回-1(负),N-position即函数返回1。对应严谨定义1:即递归到最底层返回-1的条件  定义2:只要有一步对方返回-1,则函数返回1  定义3:如果所有走法对方都胜,则我负。
 
Nim定理结论:(Bouton's Theorem):对于一个Nim游戏的局面(a1,a2,...,an),它是P-position当且仅当a1^a2^...^an=0,其中^表示异或(xor)运算。
 
Nim定理证明:
根据定义,证明一种判断position的性质的方法的正确性,只需证明三个命题: 1、这个判断将所有terminal position判为P-position;2、根据这个判断被判为N-position的局面一定可以移动到某个P-position;3、根据这个判断被判为P-position的局面无法移动到某个P-position。

第一个命题显然,terminal position只有一个,就是全0,异或仍然是0。

第二个命题,对于某个局面(a1,a2,...,an),若a1^a2^...^an!=0,一定存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。不妨设a1^a2^...^an=k,则一定存在某个ai,它的二进制表示在k的最高位上是1(否则k的最高位那个1是怎么得到的)。这时ai^k<ai一定成立。则我们可以将ai改变成ai'=ai^k,此时a1^a2^...^ai'^...^an=a1^a2^...^an^k=0。

第三个命题,对于某个局面(a1,a2,...,an),若a1^a2^...^an=0,一定不存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。因为异或运算满足消去率,由a1^a2^...^an=a1^a2^...^ai'^...^an可以得到ai=ai'。所以将ai改变成ai'不是一个合法的移动。证毕。
 
关于命题2:即k^k = 0,所以要让ai ----> ai^k ,最终异或结果即为0,而每次都要取石子数>0,所以剩下的 ai' < ai 。

Nim游戏中玩家改变的是每堆石子数目,而本题玩家通过改变小和尚的位置改变两个小和尚位子间距。
注意这里小和尚的位置是两两成对考虑的,比如1 5 8 10 考虑1-5和8-10间距,因为5-8间距对博弈结果没有影响:5位置向前1位置可以跟随。而如果位置是奇数则最后一个间距以0处理。
且石子数目只能减小,而小和尚位置可能增大,比如5向前后1位置与其间隔增大。
实现代码
#include<cstdio>

const int Max_N = 100;

int N;
int position[Max_N];//输入位置

int a[Max_N/2]; //两两位置间距

void solve()
{
    //初始化a[]
    int len = 0;
    for( int i=0; i+1<N; i+=2 )
    {//计算两两间距 
        a[len++] = position[i+1] - position[i] - 1;
    }
    if( N%2 )    a[len] = 0;  //位置为奇数
    else    len--;  //len为最大下标
    
    int k = a[0]; //计算异或
    for( int i=1; i<=len; i++)
    {
        k ^= a[i];    
    } 
    
    if( k==0 )
    {//P-position局面 先手必败
        printf("%d\n",-1);
        return; 
    }
    
    for( int i=0; i<=len; i++)
    {
        int a_ = a[i]^k;  //从小到大一次判断
        
        if( a_<a[i] )
        {//间距减小 2*i位置向前移动 
            printf("%d %d\n",position[2*i], position[2*i]+a[i]-a_);    
            return; 
        } 
        
        if( a_>a[i]&&i<len )
        {//间距增大  2*i+1向前移动 
            int length = position[2*i+2] - position[2*i+1] - 1;
            if( a_-a[i] < length )
            {
                printf("%d %d\n",position[2*i+1], position[2*i+1]+a_-a[i] );
                return;    
            } 
        }
    }
} 

int main()
{
    N = 0;
    while( scanf("%d",&position[N++])!=EOF );
    N--; //N是position[]长度 因为多加了一个EOF(-1)所以再减去  
    
    solve();
    
    return 0; 
}

//输入结束判定是EOF 如果要自己验证可以读入文件 或者用Ctrl+z表示输入结束 

 
posted @ 2020-06-30 16:43  代码改变头发  阅读(188)  评论(0编辑  收藏  举报