博弈学习 1


看完三位博主的文章再做了几个简单题后的简单总结。
可以看原文,比较详细~~

链接 1: http://blog.csdn.net/logic_nut/article/details/4711489
链接 2: http://blog.sina.com.cn/s/blog_83d1d5c70100y9yd.html
链接 3: http://blog.csdn.net/luomingjun12315/article/details/45479073

 

几个定义与结论:

(1)组合游戏

1、有且仅有两个玩家
2、两名玩家交替在 有限的移动集合(比如:取石子游戏,石子是有限的,棋盘中的棋盘大小的有限的)中对游戏进行合法的移动(不能不移动) 
3、如果轮到某名选手移动,且这个局面的合法的移动集合为空(也就是说此时无法进行移动),则这名选手负。

 

(2) Nim游戏: 
它是组合游戏(Combinatorial Games)的一种,准确来说,属于“Impartial Combinatorial Games”(以下简称ICG)。

通常的Nim游戏的定义是这样的:有若干堆石子,每堆石子的数量都是有限的,
合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时所有的石子堆都已经被拿空了,则判负(因为他此刻没有任何合法的移动)。

 

(3)两个状态


P-Position : P 指的是 Previous ,上一次移动的人有必胜策略,即先手必败
N-Position :N 指的是 Next ,这次移动的人有必胜策略,即先手必胜

1. 无法进行任何移动的局面(也就是terminal position)是P-position;
2. 可以移动到P-position的局面是N-position。 
3. 所有移动都导致N-position 的局面是P-position。

(我的理解是:如果 所有可以移动到此局面 的局面都是N-Position ,那么此局面是 P-Position;

 如果一个局面的状态是 P-Position, 那么这个局面能够转移到的所有局面都是 N-Position

 如有不对望指正^ ^)。

 

这个可以结合链接 1 原文想一想 ~

 

一个结论
(Bouton's Theorem)对于一个Nim游戏的局面(a1,a2,...,an),它是P-position当且仅当a1^a2^...^an=0,其中^表示异或(xor)运算。

证明在链接 1 中~

 

 

题目:HDU1846  2147 2149 2188

几个题都是简单的巴什博奕。

巴什博奕(Bash Game):有一堆n个物品,两人轮流从堆中取物品,每次取 x 个 ( 1 ≤ x ≤ m)。最后取光者为胜。

 

 

HDU 1846

1) 可以用上述的 P-Position 和 N-Position的定义解,多模拟几次就可以发现规律,然后就可愉快解题了。

例如 n m 分别为 6 2时
即石子有 6 颗,两人轮流取,每次可以可取 1~2 颗 。

那么所有的状态应该是这样的:

有x颗时  0 1 2 3 4 5 6
Position P N N P N N P

发现规律了咩~


2) 也可以这样想 ,复制一段来自链接 3 的推导:

如果 n = m + 1, 一次至多取 m 个,所以无论先取者,取了多少个,一定还剩余 x 个( 1 ≤ x ≤ m)。所以,后取者必胜。
因此我们发现了取胜的秘诀:如果我们把 n 表示为 n = (m + 1) * r + s 。(0 ≤ s < m , r ≥ 0)。
先取者 拿走 s 个, 后取者 拿走 k 个 (1 ≤ k ≤ m),那么先取者 再 拿走 m + 1 - k 个。
结果还剩下 ( m + 1 ) * ( r - 1 ) 个。我们只要始终给对手留下 m + 1 的倍数,那么 先取者 肯定必胜。
现在 我们可以知道,如果 s = 0,那么后取者必胜。 否则 先取者 必胜。

 

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
using namespace std;
int main()
{
    int t,n,m;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m);
        if(n%(m+1) == 0) printf("second\n");
        else printf("first\n");
    }
    return 0;
}

 


HDU2147

题意:

N*M棋盘,最初棋子在右上角,即(1,M) 每次可以往左、下、左下走一步,最后走到左下角的胜 。

最先开始 把它看做一个类似取石子的游戏,每次可走1~2步的(走到左下算两步= =)

然后 WA 掉了 WA 掉了 掉了 了。。

正解是又照着定义模拟了一遍 ,找出规律,即当 N M 都是奇数的时候先手必败。

 

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
using namespace std;
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(n==0 && m==0) break;
        if((n&1) && (m&1)) printf("What a pity!\n");
        else printf("Wonderful!\n");
    }
    return 0;
}

 


HDU2188
与 HDU 1846 同理哟

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
using namespace std;
int main()
{
    int t,n,m;
    cin>>t;
    while(t--){
        cin>>n>>m;
        if(n%(m+1)) cout<<"Grass"<<endl;
        else cout<<"Rabbit"<<endl;
    }
    return 0;
}

 


HDU 2149

按照上面的推导 ,如果我们把 n 表示为 n = (m + 1) * r + s 。(0 ≤ s < m , r ≥ 0)。
那么 s 就是第一次的加价 。如果加价大于本价,也就是说先手可以直接买到了 ,那么出的价就可以是 m~n这一段里的任一。
最先开始自己没想清楚,以为 1~s 都可以,= = 后来想了下,如果不直接加到 S ,那不是给了对方可乘之机 = =

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
using namespace std;
int main()
{
    int t,n,m;
    while(scanf("%d%d",&m,&n)!=EOF){
        if(m%(n+1)){
            int x = m%(n+1);            
            if(n>=m){
                cout<<m;
                for(int i=m+1;i<=n;i++) printf(" %d",i);
            } 
            else printf("%d",x);                        
        }
        else printf("none");    
        cout<<endl;    
    }
    return 0;
}

 

posted @ 2016-07-22 21:33  Ember  阅读(253)  评论(0编辑  收藏  举报