NIM游戏是博弈论中的经典模型。就是常见的取石子游戏,一般是不能再取石子的一方输,不能不取。
两个状态:P-position,先手必败 必败点。N-position,先手必胜 必胜点。
(根据自身的理解,用较直白的说法解释下面三条定义,下面的必败点和必胜点指先手方)
严格定义:①首先,无法进行任何移动的是必败点;
②从第一条定义的必败点中,任何可以移动到必败点的状态为必胜点
(即移动后将必败状态转移给对手,那么未操作前的状态是必胜点);
③所有移动都导致对手必胜的状态是必败点。
结论:对于状态(a1,a2,...,an),它是必败点当且仅当a1^a2^...^an=0。
证明:xor运算是怎么和NIM扯上关系的呢?必然,NIM满足的三个条件xor也满足。且通过xor=0表示必败状态。
①无法进行如何移动,那么a1,a2,...,an全为0,它们异或后也是0;
②当a1^a2^...^an!=0时,一定存在移动使ai变为ai'后a1^a2^...^ai'^...^an=0。
为什么?首先设a1^a2^...^an=k。则一定存在ai,它的二进制在k的最高位上是1(否则k的最高位上的那个1是怎么得到的)。
这时ai^k<ai一定成立。通过移动将ai变为ai'=ai^k,此时a1^a2^...^ai'^...^an=a1^a2^...^an^k=0;
③所有移动都导致a1^a2^...^an=0后,一定不存在移动,使ai变为ai'后a1^a2^...^ai'^...^an=0(将必败点转移到对手)。
因为异或满足消去律,a1^a2^...^an=a1^a2^...^ai'^...^an。那么ai'=0,为非法移动。
这些定理都是经过前人的努力,做出的经验总结。定义用的都是最精简的文字概括出整个定理,一字不多一字不少。而异或恰巧满足了这个定理的三个定义。不得不感叹二进制的神秘。想起偶然间瞄到的一句:这个世界本来就是二进制的,人非要主观构建一个十进制。
两道nim题
poj2975:http://poj.org/problem?id=2975
题意:给定n堆石子,如果你在必败点输出0,反之输出可以必胜的取石子方案数。
题解:先将n堆石子异或得num。若num=0,则必败。反之,要让num=0(将必败状态转移给对手),就是选取一堆石子。使得移动后剩下的石子异或值为0。那么现在就变成了求对于这n堆石子满足这样的操作有几堆。首先,a[i]^a[i]=0。那么num^a[i]=temp。这个temp就是除第i堆石子外的石子异或的结果。那么我在第i堆石子进行移动使其变为temp,那么剩下所有石子的异或值就为0(必败状态就转移到了对手)。所以,若a[i]>temp,这个操作就是合法的。
代码:
#include<set>
#include<map>
#include<stack>
#include<queue>
#include<vector>
#include<string>
#include<bitset>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<iomanip>
#include<iostream>
#define debug cout<<"aaa"<<endl
#define mem(a,b) memset(a,b,sizeof(a))
#define LL long long
#define MIN_INT (-2147483647-1)
#define MAX_INT 2147483647
#define MAX_LL 9223372036854775807i64
#define MIN_LL (-9223372036854775807i64-1)
using namespace std;
const int N = 1000 + 5;
const int mod = 1000000000 + 7;
int a[N],n,num,ans;
int main(){
while(~scanf("%d",&n)&&n){
num=0,ans=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
num^=a[i];
}
for(int i=1;i<=n;i++){
if(a[i]>(num^a[i])){//num^a[i]为除第i堆石子外的异或值 注意优先级要加括号
ans++;
}
}
printf("%d\n",ans);
}
return 0;
}
CodeForces - 768E:http://codeforces.com/problemset/problem/768/E
nim游戏变形:对于一堆石子,一次拿了x个之后,下次就不能再拿x个了。
题解:其实对于nim问题,异或的是每堆石子的最大数量。那这个问题的最大数量是什么呢?每堆石子按1,2,3,...,n的方式取,到剩下石子数量小于等于n之后的n就是最大数量。异或这个最大数量即可。
代码:
#include<set>
#include<map>
#include<stack>
#include<queue>
#include<vector>
#include<string>
#include<bitset>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<iomanip>
#include<iostream>
#define debug cout<<"aaa"<<endl
#define mem(a,b) memset(a,b,sizeof(a))
#define LL long long
#define MIN_INT (-2147483647-1)
#define MAX_INT 2147483647
#define MAX_LL 9223372036854775807i64
#define MIN_LL (-9223372036854775807i64-1)
using namespace std;
const int N = 1000 + 5;
const int mod = 1000000000 + 7;
int n,num,ans,temp,x;
int main(){
scanf("%d",&n);
num=0,ans=0;
for(int i=1;i<=n;i++){
scanf("%d",&x);
for(int j=1;;j++){
x-=j;
if(x<=j){
num^=j;
break;
}
}
}
if(num)
puts("NO");
else
puts("YES");
return 0;
}