HDU 5795 A Simple Nim 详解(SG打表找规律,博弈好题)

20 篇文章 1 订阅




A Simple Nim

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 973    Accepted Submission(s): 569


Problem Description
Two players take turns picking candies from n heaps,the player who picks the last one will win the game.On each turn they can pick any number of candies which come from the same heap(picking no candy is not allowed).To make the game more interesting,players can separate one heap into three smaller heaps(no empty heaps)instead of the picking operation.Please find out which player will win the game if each of them never make mistakes.
 

Input
Intput contains multiple test cases. The first line is an integer  1T100 , the number of test cases. Each case begins with an integer n, indicating the number of the heaps, the next line contains N integers  s[0],s[1],....,s[n1] , representing heaps with  s[0],s[1],...,s[n1]  objects respectively. (1n106,1s[i]109)
 

Output
For each test case,output a line whick contains either"First player wins."or"Second player wins".
 

Sample Input
  
  
2 2 4 4 3 1 2 4
 

Sample Output
  
  
Second player wins. First player wins.
 

Author
UESTC
 

Source
 

Recommend
wange2014
 

Statistic |  Submit |  Discuss |  Note

题意:给定n堆石子,每次有两种操作,1是从任意一堆中取任意个,2是把一堆分为三堆且这三堆都不为空,问谁能赢。
题解:首先看题目的数据范围是1e9,那么算出每个sg肯定会超时,所以我们想到打表找规律。首先我们暴力打表算出200以内的sg函数值,对于一堆石子来说处理出他的所有后继情况。如果只是取石子的话,那么sg函数很容易算,但如果分成三堆呢?那sg函数怎么算?那就类比尼姆博弈啊,这一堆x可以到达的状态是,分成任意三堆,这三堆相当于一个尼姆博弈,求他们的值就是抑或起来。。或者可以这样想,一堆数量x分成三堆,相当于游戏又分成了三小堆,为了求x的sg值,肯定是这三小堆(x的后继)sg抑或起来,变成了一个局部的sg基础题(可以参考HDU 1848做法 http://blog.csdn.net/qq_34374664/article/details/53432254
一种打表方式:
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 1e6 + 5;
int sg[maxn], book[maxn];
void init()
{
    for(int i = 1; i <= 200; i++)
    {
        memset(book, 0, sizeof(book));
        for(int x = 0; x < i; x++)  //这是取完之后剩下的堆数,即可到达的地方
            book[sg[x]] = 1;
        for(int j = 1; j < i; j++) //枚举三堆所有的可能性,也是可到达的地方,分成三堆要^
        {
            for(int jj = 1; jj < i; jj++)
            {
                if(i-j-jj > 0)
                    book[sg[i-j-jj]^sg[j]^sg[jj]] = 1;
            }
        }
        for(int j = 0; book[j]; j++)  //注意找mex时候j从0开始。。
        {
            sg[i] = j + 1;
        }
    }
    for(int i = 0;i <= 200; i++)
        printf("sg[%d] : %d\n" ,i, sg[i]);
}
int main()
{
 //   init();
   int t, n, x;
   scanf("%d", &t);
   while(t--)
   {
       scanf("%d", &n);
       int ans = 0;
       for(int i = 1; i <= n; i++)
       {
           scanf("%d", &x);
            if(x%8 == 0)
                ans ^= x - 1;
            else if(x%8 == 7)
                ans ^= x + 1;
            else
                ans ^= x;
       }
       if(ans)
        printf("First player wins.\n");
       else
        printf("Second player wins.\n");
   }
    return 0;
}
另一种打表方式:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 1e3+5;
typedef long long LL;
int sg[MAXN];
int get_SG(int x)
{
    if(sg[x]!=-1)  //记忆化一下
        return sg[x];
    int hash[MAXN];
    memset(hash, 0, sizeof(hash));
    for(int i=x-1; i>=0; i--)
        hash[get_SG(i)]=1;
    for(int j=1; j<=x; j++)
    {
        for(int jj=1; jj<=x; jj++)
        {
            if(x-j-jj > 0)
                hash[get_SG(x-j-jj)^get_SG(j)^get_SG(jj)]=1; //把分成三小堆当成每一堆去想
        }
    }
    int k;
    for(k=0; k<MAXN; k++)
    {
        if(!hash[k])
        {
            return sg[x] = k;
        }
    }
}
int main()
{
    /**测试打表**/
 
    /*memset(sg,-1,sizeof(sg));
    for(int i=0; i<50; i++)
    {
        printf("sg[%d] = %d\n",i,get_SG(i));
    }*/
    int T;
    cin>>T;
    while(T--)
    {
        int m;
        LL ans = 0;
        cin>>m;
        while(m--)
        {
            LL x;
            cin>>x;
            if(x%8==0)
                ans ^= (x-1);
            else if(x%8 == 7)
                ans ^= (x+1);
            else
                ans ^= x;
        }
        if(ans)
            puts("First player wins.");
        else
            puts("Second player wins.");
    }
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值