(一)巴什博奕(Bash Game):只有一堆n个物品,两个人轮流从这堆物品中取物,规
定每次至少取一个,最多取m个。最后取光者得胜。
n=(m+1)r+s
(二)威佐夫博奕(Wythoff Game):有两堆各若干个物品,两个人轮流从某一堆或同
时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
奇异局势(先手输)。前几个奇异局势是:(0,0)、(1,2)、(3,5)、(4,7)、(6,
10)、(8,13)、(9,15)、(11,18)、(12,20)
ak =[k(1+√5)/2],bk= ak + k (k=0,1,2,…,n 方括号表示取整函数)
if(n<m) swap(n,m);
int k=n-m;
n=(int)(k*(1+sqrt(5))/2.0);
if(n==m) printf("0\n");
else printf("1\n");
(三)尼姆博奕(Nimm Game):有三堆各若干个物品,两个人轮流从某一堆取任意多的
物品,规定每次至少取一个,多者不限,最后取光者得胜。
取完胜
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int main(){
int n;
int a[22];
while(scanf("%d",&n)!=EOF){
int out=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
out^=a[i];
}
if(out==0) printf("No\n");
else printf("Yes\n");
}
return 0;
}
取完负
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int main(){
int t;
scanf("%d",&t);
while(t--){
int n;
scanf("%d",&n);
int ans=0;
int flag=0;
for(int i=0;i<n;i++){
int x;
scanf("%d",&x);
ans^=x;
if(x>1) flag=1;
}
if((ans&&!flag)||(!ans&&flag)) printf("Brother\n");
else printf("John\n");
}
return 0;
}
(四)SG博奕(通用)
我们把整个博弈过程抽象为有向无环图
mex求最小非负整数mex{} = 0,mex{0,1,2,4} = 3,mex{1,2,4} = 0
sg[x] =mex{sg[y]|y是x的后继}//就是石头变少的继
这样sg就满足几个性质
sg[x] == 0时,它的后继都不为零
sg[x] != 0时,它的后继一定有为零的
当x点没有出边时,sg[x] == 0
这三个性质恰好与P-positon(先手必败)的性质相同:
(1).无法进行任何移动的局面(也就是terminal position)是P-position;
(2).可以移动到P-position的局面是N-position;
(3).所有移动都导致N-position的局面是P-position。
由此可知:sg[x] == 0,x就是p-position
eg-hdu3980
一个圆环N个数,每次每个人只能给连续的M个数抹漆,最后不能抹的就算输
比如(5,2) +++++,
当前边空0个时子游戏为(0,2)(3,2)
当前边空1个时子游戏有(1,2)(2,2)
当前边空2个时子游戏有(2,2)(1,2)
当前边空3个时子游戏有(3,2)(0,2)
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<queue>
#include<vector>
#include<map>
#include<stack>
#include<set>
using namespace std;
typedef long long LL;
int SG[1005];
int getSG(int n,int m){
if(n<m) return SG[n]=0;
if(SG[n]!=-1) return SG[n];
bool visit[1005];// //下边递归了。。不要放全局去。。。
memset(visit,false,sizeof(visit));
for(int i=0;i<=n-m;i++){
visit[getSG(i,m)^getSG(n-m-i,m)]=true;
}
for(int i=0;;i++){
if(visit[i]==false){
SG[n]=i;
break;
}
}
return SG[n];
}
int main(){
int t;
scanf("%d",&t);
for(int test=1;test<=t;test++){
int n,m;
scanf("%d %d",&n,&m);
memset(SG,-1,sizeof(SG));
printf("Case #%d: ",test);
if(n<m||getSG(n-m,m)) printf("abcdxyzk\n");
else printf("aekdycoin\n");
}
return 0;
}
eg-hdu5724
给一个n*20的棋盘,先给出了一堆棋子的位置,两个人轮流着进行操作。
操作:可以将棋子移到右边的第一个空位上,(不能多隔空位)
11100之后的状态可以有。11010,10110,01110.
假如直接将这种状态看成一个数(20位,不大),所以直接状压预处理出所有状态的SG,然后N行直接多问题SG亦或。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
bool SG[2000004];
int a[24];
bool visit[24];
int pow2[22];
inline void getp(){
pow2[0]=1;
pow2[1]=2;
for(int i=2;i<=20;i++) pow2[i]=pow2[i-1]*2;
}
inline void geta(int x){
int cnt=20;
memset(a,0,sizeof(a));
while(x){
a[cnt--]=x%2;
x=x/2;
}
}
inline int getnew(int x,int i,int j){
return x-pow2[i-1]+pow2[j-1];
}
void getSG(){
memset(SG,0,sizeof(SG));
int zhuang=(1<<20)-1;
for(int i=1;i<=zhuang;i++){
geta(i);
memset(visit,0,sizeof(visit));
int k=21;
for(int j=20;j>=1;j--){
if(a[j]==0){
k=j;
continue;
}
if(k==21) continue;
visit[SG[getnew(i,21-j,21-k)]]=true;
}
for(int j=0;;j++){
if(visit[j]==false){
SG[i]=j;
break;
}
}
}
}
int main(){
getp();
getSG();
int t;
scanf("%d",&t);
while(t--){
int n;
int out=0;
scanf("%d",&n);
while(n--){
int pp=0,w,c;
scanf("%d",&c);
while(c--){
scanf("%d",&w);
pp+=pow2[20-w];
}
out^=SG[pp];
}
if(out) printf("YES\n");
else printf("NO\n");
}
return 0;
}
eg–hdu5795
两个人轮流从n堆石子里取走任意个(至少一个),或则不拿走石子,但是讲石子分成3堆。每堆至少为一个。最后没有石子去的人输。对于如果没有第二种分石子情况就是nim博弈,但是加了第二种状态后就不是了。因此对于每一堆石子都因该有一个SG值,因此直接对一堆石子打表SG然后找下规律就可以发现。当x%8==0 sg=x-1,当x%8==7 sg=x+1,其他的sg=x。所以所有堆sg亦或就是答案。
#include<cstdio>
#include<cstring>
using namespace std;
int SG[10006];
int getSG(int x){ //针对于一堆而言
if(SG[x]!=-1) return SG[x];
if(x==0) return 0; //0个可以输
if(x==1) return 1; //1个可以赢
int visit[10006];
memset(visit,0,sizeof(visit));
for(int i=0;i<x;i++) visit[getSG(i)]=1; //当成一堆时的可到状态
for(int i=1;i<x;i++){ //分成3堆
for(int j=i;j<x;j++){
for(int k=j;k<x;k++){
if(i+j+k==x){ //子问题的亦或是可到状态
visit[getSG(i)^getSG(j)^getSG(k)]=1;
}
}
}
}
for(int i=0;;i++){
if(visit[i]==0){
return i;
}
}
}
int n;
int x;
int main(){
/*
memset(SG,-1,sizeof(SG));
for(int i=1;i<100;i++){
SG[i]=getSG(i);
printf("%2d:%d\n",i,SG[i]);
}*/
int t;
scanf("%d",&t);
while(t--){
scanf("%d",&n);
int ans=0;
for(int i=1;i<=n;i++){
scanf("%d",&x);
if(x%8==0) ans^=(x-1);
else if(x%8==7) ans^=(x+1);
else ans^=x;
}
if(ans) printf("First player wins.\n");
else printf("Second player wins.\n");
}
return 0;
}
eg–hdu4111
题意:有一堆的数字,两种操作:1:将某一个数值减1、2:将两个数字合并
解法:SG[i][j]表示有i个石子数为1的堆数,其它堆合并再取完的步数为j。直接SG扫就好了。。。
有几个地方重点说一下,我最开始也是没理解,后来懂了。。
(1)if(one>=1&&!getSG(one-1,sumstep)) return SG[one][sumstep]=1;
比赛时和菊苣手动模拟了一下,其实每一种状态,不管是怎样合并或取数只要子状态里出现了先手必输的状态,因此该状态可以转移到让对手必输,所以自己必赢。。。(也就只有这个点比赛时发现了)
(2) if(one>=1&&sumstep>=1&&!getSG(one-1,sumstep+1)) return SG[one][sumstep]=1;
将一个1合并到非1的上去,其他的操作步数为什么时+1,而不是2(合并+去掉),因为+2其实就是他本身的等价状态并不是子状态。+1(去掉)才是子状态。同理类推第四种情况的时候。
#include<bits/stdc++.h>
using namespace std;
int SG[55][55000];
//SG[i][j]表示有i个石子数为1的堆数,其它堆合并再取完的步数为j。
//若值为1则先取者胜,为0为先取者输
int getSG(int one,int sumstep){
if(SG[one][sumstep]!=-1) return SG[one][sumstep];
//没有1的时候就是各取一个
if(one==0&&sumstep%2==1) return SG[one][sumstep]=1;
else if(one==0&&sumstep%2==0) return SG[one][sumstep]=0;
//当其他的步数为1时,
if(sumstep==1) return SG[one][sumstep]=getSG(one+1,0);
//只要找到一种是输的其他状态。那他就能赢
SG[one][sumstep]=0;
//1的个数去掉一个
if(one>=1&&!getSG(one-1,sumstep)) return SG[one][sumstep]=1;
//非1的个数去掉一个
if(sumstep>=1&&!getSG(one,sumstep-1)) return SG[one][sumstep]=1;
//将1的合并到非1的上去
if(one>=1&&sumstep>=1&&!getSG(one-1,sumstep+1)) return SG[one][sumstep]=1;
//将两个1的合并
if(one>=2){
//非1的为0,相当于将一个1加到另一个1上,
if(sumstep==0&&!getSG(one-2,sumstep+2)) return SG[one][sumstep]=1;
//非1的为0,相当于将两个1加到非1上,
else if(sumstep!=0&&!getSG(one-2,sumstep+3)) return SG[one][sumstep]=1;
}
return SG[one][sumstep];
}
int main(){
int t;
scanf("%d",&t);
memset(SG,-1,sizeof(SG));
for(int cas=1;cas<=t;cas++){
int n;
int one=0,sumstep=0;
scanf("%d",&n);
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
if(x==1) one++;
else sumstep+=x+1;//合并,去掉
}
if(sumstep) sumstep--; //合并为p-1次,
if(getSG(one,sumstep)) printf("Case #%d: Alice\n",cas);
else printf("Case #%d: Bob\n",cas);
}
return 0;
}
SG 模板写法
#include<bits/stdc++.h>
using namespace std;
int SG[55][55000];
int getSG(int one,int sumstep){
if(SG[one][sumstep]!=-1) return SG[one][sumstep];
if(one==0&&sumstep%2==1) return SG[one][sumstep]=1;
else if(one==0&&sumstep%2==0) return SG[one][sumstep]=0;
if(sumstep==1) return SG[one][sumstep]=getSG(one+1,0);
//SG[one][sumstep]=0;
bool visit[2];
memset(visit,false,sizeof(visit));
if(one>=1) visit[!getSG(one-1,sumstep)]=true;
if(sumstep>=1)visit[!getSG(one,sumstep-1)]=true;
if(one>=1&&sumstep>=1) visit[!getSG(one-1,sumstep+1)]=true;
if(one>=2){
if(sumstep==0) visit[!getSG(one-2,sumstep+2)]=true;
else visit[!getSG(one-2,sumstep+3)]=true;
}
if(visit[1]==true) return SG[one][sumstep]=1;//推出来的
else return SG[one][sumstep]=0;
}
int main(){
int t;
scanf("%d",&t);
memset(SG,-1,sizeof(SG));
for(int cas=1;cas<=t;cas++){
int n;
int one=0,sumstep=0;
scanf("%d",&n);
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
if(x==1) one++;
else sumstep+=x+1;//合并,去掉
}
if(sumstep) sumstep--; //合并为p-1次,
if(getSG(one,sumstep)) printf("Case #%d: Alice\n",cas);
else printf("Case #%d: Bob\n",cas);
}
return 0;
}
1306

被折叠的 条评论
为什么被折叠?



