博弈论--从 必胜点与必败点 到 SG 函数

一、必胜点与必败点

规则:当一方不能将游戏继续下去时,游戏结束,对方获胜。

必败点(P点) 前一个(previous player)选手将取胜的点称为必败点

必胜点(N点) 下一个(next player)选手将取胜的点称为必胜点

(1) 所有终结点是必败点(P点);

(2) 从任何必胜点(N点)操作,至少有一种方法可以进入必败点(P点);

(3)无论如何操作, 从必败点(P点)都只能进入必胜点(N点).

方法:

步骤1:将所有终结位置标记为必败点(P点);
步骤2: 将所有一步操作能进入必败点(P点)的位置标记为必胜点(N点)
步骤3:如果从某个点开始的所有一步操作都只能进入必胜点(N点) ,则将该点标记为必败点(P点) ;
步骤4: 如果在步骤3未能找到新的必败(P点),则算法终止;否则,返回到步骤2。


hdu2147

画出PN图,即可得结论。

二、Nim博弈

有若干堆石子,分别有N1,N2...Nm颗,2人轮流取子,每次可以任选一堆,取任意颗。最后无法再操作的人输。


解法:将所有石子数异或,N1 ^ N2 ^ ... Nm,若为0,则先手必败;不为0,则先手必胜。



hdu1850 Being a Good Boy in Spring Festival

求先手获胜,有多少种走法。


#include <iostream>

#include <cstdio>

using namespacestd;

const int maxn =100 +5;

int num[maxn];


int main()

{

    int m,cnt,ans =0;

    while (scanf("%d",&m) !=EOF && m) {

        ans = 0;

        for (int i =0; i < m; i ++) {

            scanf("%d",&num[i]);

            ans ^= num[i];

        }

        cnt = 0;

        if(ans){

        for (int i =0; i < m; i ++) {

            if (num[i] >= (ans ^ num[i])) {//若可以通过将第i堆,取走一些,使剩下的堆异或为0,则cnt++

                cnt ++;

            }

        }

        }

        printf("%d\n",cnt);

    }

    return0;

}



三、SG函数

将若干堆石子,看作一个状态,起始状态处有一颗棋子,则玩家的每次操作,就是将棋子沿有向无回图的边移动,移动至无法移动时,则游戏结束。

然后为每个状态x(即有向无回图的每个顶点),定义一个SG函数值,SG(x) = mex{ x所有后继ySG(y) }mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。

必败点P SG(P) = 0;

必胜点N SG(N) > 0;

SG(x) = k > 0,则说明,状态x的后继的sg值为0,1,...k - 1,移动xsg值可能变为0,1,..,k - 1(与Nim博弈类似,一堆k颗石子,取任意颗后,可能是0,1,..k - 1颗)

所以,有Nim博弈可以得到,如果有m颗棋子,位于m个有向无回图中,每次可以选任意一颗棋子移动,棋子sg值分别为SG(x1),SG(x2)...SG(xm),若异或值为0,则先手必败。


如何计算SG函数

1.若可以取任意颗(Nim博弈),则SG(x) = x

(所以Nim博弈可以直接将石子数异或,不用求SG


2.每次可以取1-m颗,则SG(x) = x % (1 + m)


3.给定k种操作,每次可以s1,s2...sk

//k 操作数,s[]记录每种操作可以取多少颗

void cal_sg()//预处理所有sg(x) O(maxk * maxn)

{

    memset(g, 0, sizeof(g));//SG函数

    for (int i = 1; i < maxn; i ++) {

        memset(mp, 0, sizeof(mp));//记录所有后继的sg值,求mex

        for (int j = 0; j < k; j ++) {

            if(i < s[j]) break;

            mp[g[i - s[j]]] = 1;

        }

        for (int j = 0; j < maxn; j ++) {

            if(mp[j] == 0) {g[i] = j;break;}

        }

    }

}

int get_sg(int x)//计算单独sg(x)递归求

{

    memset(g, -1, sizeof(g));

    mp[0] = 0;

    int i;

    for (i = 0; i < k; i ++) {

        if(x < s[i]) break;

        if(g[x - s[i]] == -1) g[x - s[i]] = get_sg(x - s[i]);

        mp[g[x - s[i]]] = 1;

    }

    for(i = 0;;i ++) {if(mp[i] == 0) return g[x] = i;}

}


hdu1848 Fibonacci again and again

2人3堆石子m,n,p,每次可取fibonacci数列中的个数(1,2,3,5,8....)


#include <iostream>

#include <cstdio>

#include <vector>

#include <cstring>

using namespace std;

const int maxn =1000 +5;

vector<int> fibo;

int g[maxn];

int mp[maxn];

void get_fibo()

{

    fibo.push_back(1),fibo.push_back(2);

    for (int i =2;; i ++) {

        fibo.push_back(fibo[i - 1] + fibo[i -2]);

        if(fibo[i] > maxn)break;

    }

}

void cal_sg()//预处理计算maxn内的sg值 O(maxn * k)

{

    memset(g, 0,sizeof(g));

    for (int i =1; i < maxn; i ++) {

        memset(mp, 0,sizeof(mp));

        for (int j =0; j < fibo.size(); j ++) {

            if(i < fibo[j])break;

            mp[g[i - fibo[j]]] = 1;

        }

        for (int j =0;; j ++) {

            if(mp[j] ==0) {g[i] = j;break;}

        }

    }

}

int main()

{

    int m,n,p;

    get_fibo();

    cal_sg();

    while (scanf("%d%d%d",&m,&n,&p) != EOF) {

        if(m ==0 && n ==0 && p ==0)break;

        if (g[m] ^ g[n] ^ g[p]) printf("Fibo\n");

        else printf("Nacci\n");

            

  }

    return0;

}


hdu1536 S-Nim
预处理所有sg会超时,所以需要记忆化搜索

#include <iostream>

#include <cstdio>

#include <cstring>

#include <algorithm>

using namespacestd;

const int maxk =100 +5;

const int maxn =1e4 +5;

int s[maxk];

int g[maxn];

int k;

int get_sg(int x)//计算单独sg(x)递归求

{

    if(g[x] != -1)returng[x];

    int mp[maxk] = {0};//!!!貌似在全局定义,每次memset会超时?

    int i;

    for (i =0; i <k; i ++) {

        if(x <s[i])break;

        if(g[x -s[i]] == -1)g[x - s[i]] =get_sg(x -s[i]);

        mp[g[x -s[i]]] =1;

    }

    for(i =0;;i ++) {if(mp[i] ==0)returng[x] = i;}

    

}

int main()

{

    int m,l,t,ans;

    while (scanf("%d",&k) != EOF && k) {

        memset(s,0,sizeof(s));

        memset(g, -1,sizeof(g));

        g[0] =0;

        for (int i =0; i <k; i ++) {

            scanf("%d",&s[i]);

        }

        sort(s,s +k);

        scanf("%d",&m);

        for (int i =0; i < m; i ++) {

            scanf("%d",&l);

            ans = 0;

            for (int j =0; j < l; j ++) {

                scanf("%d",&t);

                ans ^= get_sg(t);

            }

            if(ans)printf("W");

            elseprintf("L");

        }

        printf("\n");

   }

    return0;

}




  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值