现在的位置: 首页 > 综合 > 正文

组合游戏_尼姆博弈 部分题型解析

2018年04月25日 ⁄ 综合 ⁄ 共 2383字 ⁄ 字号 评论关闭

好不容易放了一次假,终于得空整理一下有关组合游戏的东西了。

尼姆博弈:有m堆物品,每次选择一堆,拿走至少一件,两位选手,轮流取物品,哪位选手拿走了最后一件,哪位选手就得胜。

我们先来认识一下SG函数

SG为0值的点为必败点

尼姆博弈分为两种状况

一:每次取物的数量范围是【1,m】(m是这堆物品的总数量)

此时,不用计算SG值,每堆的物品数就是就是它所对应的SG值

如下题    HDU2176 取(m堆)石子游戏题目链接

题意:中文~~~~

一道典型的尼姆博弈题目

现给出AC代码

#include<stdio.h>
int main()
{
    int m;
    int ans;
    int data[200000+20];
    int i ;
    while(scanf("%d",&m)&&m)
    {
        for(i = 0; i < m; i++)
        {
            scanf("%d",&data[i]);
        }
        ans = data[0];
        for(i = 1; i < m; i++)//将每堆的数目直接进行异或
        {
            ans ^= data[i];
        }
        if(ans== 0)//对于异或值为0,是必败点
        {
            printf("No\n");
        }
        else
        {
            int end = 0;
            printf("Yes\n");
            for(i = 0; i < m; i++)
            {
                end = ans ^ data[i];//end是按照最优的方法取完石子后,剩下的石子
                if(end < data[i])
                {
                    printf("%d %d\n",data[i],end);
                }
            }
        }
    }
    return 0;
}

二.m堆物品,每次取的数目i必须属于某一个集合

此时,SG值不再是当前堆的物品数,而需要通过计算求得

如下题 HDU1848  Fibonacci again and again题目链接

题意:共有 三堆石子,两人轮流取石子,但是每次取石子的个数必须属于斐波那契数列,取完最后一个石子的一方获胜。

先普及一下斐波那契数列

F[0]=1,F[1]=2,F[i]=F[i-1]+[i-2],此时要求得每一点的sg值

现给出AC代码

#include<stdio.h>
int F[1010];
int f[1010];
int ct;
int mex(int x)
{
    int g[1010]={0};//bool数组表示那些值被作为某个节点的SG值使用过
    int t,i;
    int j;
    for (i=0; i<ct; i++)//此处的ct为存储可取数目的数组的长度
    {
        t=x-F[i];//数组fib为操作数集合,t为x当前遍历的后继
        if (t<0) break;
        if (f[t]==-1)  f[t]=mex(t);//若t点未赋SG值,递归查找
        g[f[t]]=1;//bool数组中,这个SG值赋为true
    }
    for (j=0; ; j++)//遍历bool数组
    if (!g[j]) return j;//第一次遇到一个未被作为SG值的数时,返回答案。
}
int main()
{
    int i,j;
    int ans;
    int n,m,p;
    F[0]=1;
    F[1]=2;
    i = 1;
    while(F[i]<1050)
    {
        F[i+1]= F[i]+F[i-1];
        i++;
    }
    ct = i;
    memset(f,-1,sizeof(f));
    f[0]=0;
    for(i = 1; i <= 1000; i++)
    {
        f[i]=mex(i);
    }
    while(scanf("%d%d%d",&n,&m,&p)&&n+m+p)
    {
        ans = 0;
        ans^=f[n];
        ans^=f[m];
        ans^=f[p];
        if(ans)
            printf("Fibo\n");
        else
            printf("Nacci\n");
    }
    return 0;
}

再来看下一道 HDU1536 S-Nim题目链接

题意:首先给出一些数字,然后给出一组事件堆,两个玩家,要求轮流从组中取得石子,去玩最后一个石子的人获胜

现给出AC代码

#include <iostream>
#include<cstdio>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAX = 110;
const int MMAX=10000+10;
int k,m,l;
int F[MAX],num;
int sg[MMAX];

int mex(int x)
{
    bool g[101];//bool数组表示那些值被作为某个节点的SG值使用过
    int t;
    memset(g,0,sizeof(g));//初值全为0
    for (int i=0; i<k; i++)//此处的k为存储可取数目的数组的长度
    {
        t=x-F[i];//数组F为操作数集合,t为x当前遍历的后继
        if (t<0) break;
        if (sg[t]==-1)  sg[t]=mex(t);//若t点未赋SG值,递归查找
        g[sg[t]]=true;//bool数组中,这个SG值赋为true
    }
    for (int i=0;i<101 ; i++)//遍历bool数组
    if (!g[i]) return i;//第一次遇到一个未被作为SG值的数时,返回答案。
}

int main()
{
    int ans;
    while(scanf("%d",&k)&&k)
    {
        for(int i = 0; i < k; i++)
        {
            scanf("%d",&F[i]);
        }
        sort(F,F+k);
        memset(sg,-1,sizeof(sg));
        sg[0]=0;
        scanf("%d",&m);
        while(m--)
        {
            /*for(int i = 1; i < MMAX; i++)//注掉的部分没有错,但是会超时
            {
                sg[i]=mex(i);
            }*/
            ans = 0;
            scanf("%d",&l);
            while(l--)
            {
                scanf("%d",&num);
                if(sg[num]==-1)
                {
                    sg[num]=mex(num);
                }
                ans ^= sg[num];
            }
            if(ans)printf("W");
            else printf("L");
        }
        printf("\n");
    }
    return 0;
}

通过对比,可知,在这两道题目中,求SG值得mex函数是相同的。

抱歉!评论已关闭.