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
1≤T≤100
, 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[n−1]
, representing heaps with
s[0],s[1],...,s[n−1]
objects respectively.
(1≤n≤106,1≤s[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
题意:给定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;
}