【poj2068】Nim

Portal -->poj2068

Description

​   给你\(S\)个石子,有\(2n\)个人分成两队,编号为奇数的一队,编号为偶数的一队,\(2n\)个人按照编号从小到大的顺序拿石子,所有人都拿过了就再从\(1\)号轮,编号为\(i\)的人一次可以拿\(x\in[1,a[i]]\)颗,拿到最后一颗石子的队伍输,判断当前局面是否先手必胜

Solution

​   emmm今天做了几道sg函数的题然后感觉这玩意很神秘

​​   除了转化成"有向图游戏"那样的形式之后用异或和和\(mex\)\(sg\)以外,还有的题中\(sg\)的取值只有\(0\)\(1\)两种,可以直接判断是否存在一个后继局面的\(sg\)值为\(0\)(也就是先手必败态),如果有就说明当前局面\(sg\)值为\(1\)(也就是先手必胜态),因为根据P-position(先败)和N-position(先胜)的定义,可以移动到P-position的局面是N-position,所以直接这么判就好了

​​   当然你也还是可以转成 一个有向图游戏,只要后继局面中有\(0\),那么取一下\(mex\)就只能是\(1\)了,一样的

​​   这题中比较容易想到的就是用"当前是谁准备取"和"当前还剩多少石子"来表示一个局面,那直接大力记忆化搜索就好了,边界条件就是如果当前没有石子了,那么是先手必胜态

​​   最后就是求\(nxt\)的时候模数记得是\(2n\)而不是\(n\)。。。

​   (一开始陷入了一个误区。。就是觉得每个人的取石子上限不同,所以不是一个ICG,但其实ICG中只是要求移动集合(在这题里也就是能移哪些石子)不与选手相关,并没有限制具体操作)

​  

​   代码大概长这个样子

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=9000;
int f[60][N],a[60];
int vis[N];
int n,m,S,mark;
int nxt(int x){return (x+1)%(2*n)==0?2*n:(x+1)%(2*n);}
int sg(int x,int stone){
	if (f[x][stone]!=-1) return f[x][stone];
	if (stone==0) return f[x][stone]=1;
	int tmp=nxt(x);
	for (int i=1;i<=a[x]&&i<=stone;++i){
		if (!sg(tmp,stone-i))
			return f[x][stone]=1;
	}
	return f[x][stone]=0;
}

int main(){
#ifndef ONLINE_JUDGE
	freopen("a.in","r",stdin);
#endif
	while (1){
		scanf("%d",&n);
		if (n==0) break;
		memset(f,-1,sizeof(f));
		scanf("%d",&S);
		mark=0;
		for (int i=1;i<=n*2;++i)scanf("%d",a+i);
		printf("%d\n",sg(1,S));
	}
}
posted @ 2018-08-05 20:25  yoyoball  阅读(140)  评论(0编辑  收藏  举报