题意:已知一排硬币中有n个硬币正面朝上,输入正面朝上的硬币的位置ai。两人轮流操作,每次操作可以翻转1,2,或则3枚硬币,其中翻转的最右的硬币必须是正面朝上的,最后不能翻转的为负。理解题意理解了半天,还差点理解错了。。
题解:我们发现这是个Nim游戏,简单化题目,就是将一个游戏分成多个子游戏。这题是分出的子游戏是,只有一个正面朝上的硬币,位置在ai,求出所有该类sg函数值sg[ai]。则答案就是ans=sg[a1]^sg[a2]^...^sg[an]。
接着就是求单个sg的函数值,由于ai是从0开始,而当ai=0时,只有翻转“正”到“反”(必败),所以sg[0]=1;之后可以for一遍求出之后的sg函数值了,由于ai必定变为反,所以后继状态必定是已知的sg函数值,详细见代码。
又ai很大,所以只能找规律。这个考验眼力和想象能力了。。sg[x]=2*x或则sg[x]=2*x+1。从这出发,我们又发现所有sg函数值二进制表示的数中1的个数为奇数个。这样就简单了,对于输入的ai,判断下2*ai的二进制表示中1的个数奇偶性就OK。
之后就是SG定理的运用,其实就是异或下,一切OK。。。。。
,题解代码:
#include <cstdio>
#include <cstring>
#include <map>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=1e6+10;
map<int,int>mm;
bool judge(int x)
{
int s=0;
while(x)
{
if(x&1)s++;
x=x>>1;
}
if(s%2)return true;
return false;
}
int find(int x)
{
if(judge(2*x))return 2*x;
return 2*x+1;
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
mm.clear();
int ans=0,i,j,a;
for(i=0;i<n;i++)
{
scanf("%d",&a);
if(mm[a]==0)//去重
ans=ans^find(a);
mm[a]=1;
}
if(ans==0)printf("Yes\n");
else printf("No\n");
}
return 0;
}
sg定理代码,sg函数值打表+找规律:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=1e4+10;
int sg[maxn],vis[maxn];
void init()
{
int i,j,k;
sg[0]=1;
for(i=1;i<=1000;i++)
{
memset(vis,0,sizeof(vis));
vis[0]=1; //翻一个硬币,后继必败
for(j=0;j<i;j++)
vis[sg[j]]=1; //翻两个硬币
for(j=0;j<i;j++)
for(k=0;k<j;k++)
vis[sg[j]^sg[k]]=1;//翻三个硬币
for(j=0;;j++)
if(!vis[j])break;
sg[i]=j;
}
for(i=1;i<=20;i++)
cout<<i<<":"<<sg[i]<<endl;
}
int main()
{
init();
}