NIM游戏-博弈论(poj2975+CodeForces - 768E)

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;
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值