Live2d Test Env

【博弈初步】

基本常见的路人皆知的博弈

巴什博奕(Bash Game);威佐夫博奕(Wythoff Game);尼姆博奕(Nimm Game)。 此外,还有翻硬币,删边等。 当然,不乏一些变态数学题。

基础博弈高中是学习过,但是过于基础,现在强化博弈方面。

一般的博弈最后取者胜。自然还有最后取者输的。

博弈的输赢关键在于找到XXX态(必输、必赢、S态?T态之类的东西)。然后看能不能自己面对必赢态,然后取后把必输态留给对手。

 

注意:一般的博弈题不能普通搜索(直接搜索是否可以再轮到自己时达到某状态)解决,因为两个玩家都是高智商,双方都是采取最优解。

现在的状态应该是:自己可以将面对的状态变为必败,则为必赢;若不能则为必败。 若从一张图上看,就是G状态后序节点中有一个必败节点,则它自己为必胜节点.....然后可能就需要SG函数。

【参考】

博客经典讲解:http://blog.csdn.net/acm_cxlove/article/details/7854526 (cxlove)。

SG函数详细:https://www.cnblogs.com/ECJTUACM-873284962/p/6921829.html

SG函数通俗证明:http://blog.csdn.net/strangedbly/article/details/51137432

二分图博弈:https://www.cnblogs.com/jffifa/archive/2011/12/17/2291069.html (2018-3-25补充)

著名进阶讲解:Thomas S. Ferguson(群里有)。

 等做够了题,把难度分级后再上题。

待续-------------------------------------------------------------------------

 

【基础博弈】

一:翻硬币游戏(Nim博弈) 

题目大意:
N个硬币排成一排,已知是正面(H)还是反面(T)。现在两个人轮流翻,每次只能选择一枚正面(H)的硬币翻到反面,
同时可以选择一枚左边的硬币翻转(此时不管正反都可以翻)。 没有可以翻的人输。
思路:
我们规定,只有第i个硬币正面向上,则为状态i,全部向下为0。那么状态i可以变为状态0,1,2....i-1。分别表示0:翻转第i个,不翻转左边的;
1:翻转第i个,顺便把第1个也翻转...
但是情况不单单是只有一枚i硬币朝上。但是不重要,如果第j<i枚也朝上,那么翻转第i个,顺便翻转第j个的情况就是j^j=0就行了。
所以状态X可以变为0,1,2,,,,x-1,这和Nim的规则是一样的。
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=11000;
char chr[maxn];
int main()
{
    int N,Xor,i;
    while(~scanf("%d",&N)){
        scanf("%s",chr+1);
        Xor=0;
        for(i=1;i<=N;i++){
            if(chr[i]=='H') Xor^=i;
        }
        if(Xor) printf("Alice\n");
        else printf("Bob\n");
    } return 0;
}
View Code

 

 二:John

题意:和Nim一样的规则,但是是取到最后一个石子的人输。
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cstring>
using namespace std;
int main()
{
    int T,g,x,n,i,j;
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        x=g=0;
        for(i=1;i<=n;i++){
            scanf("%d",&j);
            x^=j; g+=(j>1?1:0);
        }
        if((!g&&x&1)||(g>1&&!x)) printf("Brother\n");
        else printf("John\n");
    } return 0;
}
View Code

 

三:Be the Winner

题意:有一排连续的石子,每次可以取任意连续的石子,取到最后一个的输。
思路:参照上题
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cstring>
using namespace std;
int main()
{
    int g,x,n,i,j;
    while(~scanf("%d",&n)){
        x=g=0;
        for(i=1;i<=n;i++){
            scanf("%d",&j);
            x^=j; g+=(j>1?1:0);
        }
        if((!g&&x)||(g>1&&!x)) printf("No\n");
        else printf("Yes\n");
    } return 0;
}
View Code

 

四:Being a Good Boy in Spring Festival

题意:问Nim博弈中,先手胜的情况下,第一步有几种取石子方案。
思路:实验一下每一堆即可。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;
int p[110];
int main()
{
    int n,Xor,i,ans;
    while(~scanf("%d",&n)){
        if(n==0) return 0;
        Xor=0;
        for(i=1;i<=n;i++){
            scanf("%d",&p[i]);  
            Xor^=p[i];
        }
        if(!Xor) printf("0\n");
        else {
            ans=0;
            for(i=1;i<=n;i++){
                if((Xor^p[i])<=p[i]) ans++;
            }
            printf("%d\n",ans);
        }
    } return 0;
}
View Code

 

五:A Simple Game

题意:有N堆石子,Nim规则,但每一堆有上限Li,即单次不能超过Li个。 
思路:每一堆mod(Li+1),即Xi>(Li+1)时,利用Bush博弈,后面的再Nim博弈。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;
int main()
{
    int T,n,Xor,i,ans,M,L;
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        Xor=0;
        for(i=1;i<=n;i++){
            scanf("%d%d",&M,&L);  
            Xor^=(M%(L+1));
        }
        if(Xor) printf("No\n");
        else printf("Yes\n");
    } return 0;
}
View Code

 

取石子游戏

题意:先手不能一次性取完,下一个人取的数量不能超过上一位的两倍。
思路:斐波拉契博弈。“Zeckendorf定理”(齐肯多夫定理):任意数可以表示位多个斐波拉契数的和,而且他们的位置不相邻。 先手每次取后变成的新数,表示成若干个斐波拉契
数的和,从小到大(由于是不相邻的斐波拉契数,后面一个大于前面一个的两倍),
后手都可以取某个斐波拉契数的最后一个。比如先手取后变成21,21=1+3+8;对于“1”,
后手可以取到,对于“3”,先手不能一次性取完,无论怎么取,后手都拿到最后一个,对于“8”也是同理。对于每一个斐波拉契数数,都可以由后手取到。所以斐波拉契数后者胜。

2,3是后手胜利,即斐波拉契数胜。 若不是斐波拉契数,先手可以取部分使之变成斐波拉契数,先手胜。
#include<map>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
map<int,int>mp;
int main()
{
    int i,N,a[100];
    a[0]=2;a[1]=3;
    mp[2]=1; mp[3]=1;
    for(i=2;i<45;i++){
        a[i]=a[i-1]+a[i-2];
        mp[a[i]]=1;
    }
    while(~scanf("%d",&N)){
        if(N==0) return 0;
        if(mp.find(N)!=mp.end()) printf("Second win\n");
        else printf("First win\n");
    }
} 
View Code

 

 

 

【其他博弈】

 一:Euclid's Game 

题意:
两人博弈,给出两个数a和b,较大数减去较小数的任意倍数,结果不能小于0,将两个数任意一个数减到0的为胜者。
思路:假设 a大于b a == b.  N态 a%b == 0. N态 
         a >= 2*b,先手能决定谁取(b,a%b),并且知道(b,a%b)是P态还是N态.    N态
         b< a< 2*b, 只能 -->(b,a-b) , 然后再进行前面的判断.
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;
int main()
{
    int num,a,b;
    while(~scanf("%d%d",&a,&b)){
        if(a==0&&b==0) return 0;
        num=1;
        while(true){
            if(a<b) swap(a,b);
            if(a%b==0||b==0) break;
            if(a>=2*b) break;
            a=a%b;
            num++;
        }
        if(num&1) printf("Stan wins\n");
        else printf("Ollie wins\n");
    } return 0;
}
View Code

 二:Good Luck in CET-4 Everybody!

题意:有N个石子,每次可以取2的幂次个石子,即1,248...最后一个取到的胜利。
思路:先手取后变成3的倍数,然后后手无论取什么,都变成不是3的倍数。 直到先手取后变成3,然后无论后手取1还是2,先手胜。
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cstring>
using namespace std;
int main()
{
    int n;
    while(~scanf("%d",&n)){
        if(n%3==0) printf("Cici\n");
        else printf("Kiki\n");
    } return 0;
}
View Code

 三:Play a game

题意:两人在N*N的棋盘上走,起始在角落,轮流走,每次可以走到旁边一格,不能走重复的点,不能走动为输。
思路:发现先手每次占据一个奇点位置(如1*1),然后后手只能占据一个偶点位置(如1*2,2*1),所以N为奇时,后手会被逼死。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int n,m; 
int main()
{
    while(~scanf("%d",&n)){
        if(n==0) return 0;
        if(n&1) printf("ailyanlu\n");
        else printf("8600\n");
    } return 0;
}
View Code

 

四:游戏Game(二分图博弈)

题意:给定一个矩阵棋盘,有些地方有阻碍不能走。先手选一点设为起点,然后从后手开始,一人走一步,走不动的输。
思路:https://www.cnblogs.com/jffifa/archive/2011/12/17/2291069.html

 五:Moving Pebbles(对称博弈)

题意:任选一堆,首先拿掉至少一个石头,然后移动任意个石子到任意一些堆中. 不能移动的输。n<=100000
思路:利用对称性:注意题目中说的是移动任意石子到任意一些堆中,而非只能移动到一个堆(。。。我开始就这么以为,只要这里没有都错题,
就不难想到后者可以模仿前一位的操作。

 

【图论   SG】

一:Fibonacci again and again(简单SG)

题意:有3堆石子,每次可以从任意堆中 取出 F个(F是1,235813...),最后一个取的人胜利。
思路:SG求出来,求异或和即可。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;
int f[20],SG[1010],ex[20];
void solve()
{
    for(int i=1;i<=1000;i++)
    {
        for(int j=1;j<=15;j++) {
            if(f[j]>i) break;
            ex[SG[i-f[j]]]=i;
        }
        for(int j=0;j<=1000;j++)  
           if(ex[j]!=i){
             SG[i]=j;
             break;
        }
    }
}
int main()
{
    int n,m,p,i,j; f[1]=1;f[2]=2;
    for(i=3;i<=15;i++) f[i]=f[i-1]+f[i-2];
    solve();
    while(~scanf("%d%d%d",&n,&m,&p)){
        if(n==0&&m==0&&p==0) return 0;
        if(SG[n]^SG[m]^SG[p])  printf("Fibo\n");
        else printf("Nacci\n");
    } return 0;
}
View Code

 二取石子游戏 (SG打表找规律)

题意:有N堆石子,每次可以选择在任意一堆取任意多的石子。也可以选择任意一堆,分成两堆。没有可以操作的选手输。
思路:求出每一堆的SG函数,最后求异或和。 对于一堆有X个石子的堆,取后SG=:012...X-1;可以分:SG[1]^SG[X-1],,SG[2]^SG[X-2]...。
我们打表出前面的SG函数,发现SG函数的规律是以4为周期变化的,具体为i,i+1,i+3,i+2
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=20000;
int sg[maxn];
void solve()
{
    for(int i=1;i<=maxn;i+=4){
        sg[i]=i; sg[i+1]=i+1;
        sg[i+2]=i+3; sg[i+3]=i+2;
    }
}
int main()
{
    int N,i,x,Xor=0;
    solve(); scanf("%d",&N);
    for(i=1;i<=N;i++){
        scanf("%d",&x);
        Xor^=sg[x];
    }
    if(Xor) printf("Alice\n");
    else printf("Bob\n");
}
View Code

三:kiki's game (SG打表找规律)

题意:开始棋子在(n,m),每次可向左,或向上,或左上走一步。走到(1,1)者胜利。
思路:打表出SG图找规律。发现n和m都为奇数点才是必胜N态。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int n,m; 
int main()
{
    while(~scanf("%d%d",&n,&m)){
        if(n==0&&m==0) return 0;
        n%=2; m%=2;
        if(n==0||m==0) printf("Wonderful!\n");
        else printf("What a pity!\n");
    } return 0;
}
View Code

 

posted @ 2018-02-22 15:30  nimphy  阅读(620)  评论(2编辑  收藏  举报