您的位置:首页 > 其它

取石子游戏之尼姆博弈

2012-07-23 08:43 309 查看
尼姆博弈:有三堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。

这种情况与二进制有着很大的关系,我们用(a,b,c)来表示某种局势,那么(0,0,0)必然为奇异局势,最后一个面对这个局势的必败。(0,n,n)也是种奇异局势。因为如果对手在其中一堆取m个石子(m<=n),那么你也可以在另外一堆中取m个,他取几个你就取几个,到最后有一堆变为0的时候,你再取完另一堆胜利。

直接说结论吧:

对于任意的奇异局势(a,b,c),都有a^b^c=0。(^为异或运算)。

对于任意的非奇异局势(a,b,c),假设a<b<c,将它变为奇异局势的方法是:将c变成a^b。

原理:因为a^a=0,所以a^b^c=a^b^(a^b)=(a^a)^(b^b)=0,所以只需将c-(a^b)即可。

例1:有个非奇异局势(14,21,39),因为14^21=27,39-27=12,所以从39中拿走12个物体即可达到奇异局势(14,21,27)。

例2:我们来实际进行一盘比赛看看:

甲:(7,8,9)->(1,8,9)奇异局势

乙:(1,8,9)->(1,8,4)

甲:(1,8,4)->(1,5,4)奇异局势

乙:(1,5,4)->(1,4,4)

甲:(1,4,4)->(0,4,4)奇异局势

乙:(0,4,4)->(0,4,2)

甲:(0.4,2)->(0,2,2)奇异局势

乙:(0,2,2)->(0,2,1)

甲:(0,2,1)->(0,1,1)奇异局势

乙:(0,1,1)->(0,1,0)

甲:(0,1,0)->(0,0,0)奇异局势

甲胜。

性质1:对于某个局面(a1,a2,...,an),若a1^a2^...^an!=0,一定存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。

分析:令res=a1^a2^......an,设res的最高位为pos(那pos的数肯定为1啦),那么一定存在某个ai,它的二进制在pos位上也是1,(否则k的最高位那个1是怎么得到的)。异或res后这位变为0,这时ai^res<ai一定成立。则我们可以将ai改变成ai'=ai^res,此时a1^a2^...^ai'^...^an=a1^a2^...^an^res=0。

性质2:对于某个局面(a1,a2,...,an),若a1^a2^...^an=0,一定不存在某个合法的移动,将ai改变成ai'后仍满足a1^a2^...^ai'^...^an=0。

例题1:POJ 2975,题目意思是给你一组局势,问有多少中必胜的策略。

分析:求出res=a[0]^a[1]^a[2].....a[n-1],由于每次只能改变一堆石子的数量,由性质1,可知如果res!=0,那么一定存在某个合法的操作,使得res=0,只要满足a[i]*res<=a[i]。

#include<iostream>
using namespace std;
const int MAX=1010;
int a[MAX];
int main()
{   int n,res;
while(cin>>n,n)
{   int count=0;
for(int i=0;i<n;i++)
cin>>a[i];
res=a[0];
for(int i=1;i<n;i++)
res^=a[i];
if(res==0) cout<<0<<endl;
else
{   for(int i=0;i<n;i++)
if((a[i]^res)<a[i])  count++; //要注意^和<=的优先级,必须加括号
cout<<count<<endl;
}
}
return 0;
}

SG函数:定义mex运算为最小不属于这个集合的非负整数。mex{0,1,2,4}=3,mex{}=0。

对于一个给定的有向无环图,我们定义关于图的每个顶点的SG函数g如下:g(x)=mex{g(y) | y是x的后继}。

SG函数的性质:由于是一个有向无环图,那么对于所有的末端位置,由于没有后继,所以SG=0。另外对于一个g(x)=0的顶点x,它的所有后继y都满足g(y)!=0(由于集合中的元素是不可以重复的,所以后面的g(y)!=0)。对于一个g(x)!=0的顶点,必定存在一个后继y满足g(y)=0(这个时候最小的非负整数一定是0)。

由上可知:顶点x所代表的位置是必败点条件是:g(x)=0。

看到一个经典的话额,就贴在这里吧:

有些事,明知是错的,也要去坚持,因为不甘心;有些人,明知是爱的,也要去放弃,因为没结局;有时候,明知没路了,却还在前行,因为习惯了。

POJ 2488~八嘎
#include<iostream>

#include<cstring>

#include<vector>

using namespace std;

const int MAX=10;

#define CLR(arr,val) memset(arr,val,sizeof(arr))

int visit[MAX][MAX];

int n,m,dx[8]={-2,-2,-1,-1,1,1,2,2},dy[8]={-1,1,-2,2,-2,2,-1,1};

struct Point

{ int x,y;

};

Point Start;

vector<Point> V;

bool Inside(Point P)

{ return P.x>=1&&P.x<=m&&P.y>=1&&P.y<=n;

}

void DFS(Point P)

{ Point s;

for(int i=0;i<8;i++)

{ s.x=P.x+dx[i];

s.y=P.y+dy[i];

if(Inside(s)&&!visit[s.x][s.y])

{ visit[s.x][s.y]=1;

V.push_back(s);

DFS(s);

}

}

}

int main()

{ int Case,num=1;

cin>>Case;

while(Case--)

{ cin>>n>>m;

CLR(visit,0);

cout<<"Scenario #"<<num++<<":"<<endl;

Start.x=1,Start.y=1;

visit[1][1]=1;

V.push_back(Start);

DFS(Start);

if(V.size()==n*m)

{ for(vector<Point>::size_type i=0;i<V.size();i++)

cout<<char(V[i].x+'A'-1)<<V[i].y;

cout<<endl;

}

else cout<<"impossible"<<endl;

V.clear();

if(Case) cout<<endl;

}

return 0;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: