Nim游戏基本定理: Nim博弈先手必胜,当且仅当 A 1 x o r A 2 x o r A 3 . . . x o r A n ≠ 0 A_1 xor A_2xor A_3...xor A_n\ne0 A1xorA2xorA3...xorAn=0
公平组合游戏ICG
- 两名玩家交替行动;
- 在游戏进程的任意时刻,可以执行的合法行动与轮到哪名玩家无关;
- 不能行动的玩家判负。
有向图游戏 给定一个有向无环图,图中有一个唯一的起点,在起点上放一枚棋子,两名玩家交替的把这枚棋子沿有向边进行移动,每次可以移动一步,无法移动者判负。该游戏被称为有向图游戏,任何一个公平组合游戏都可以转化为有向图游戏。 有向图游戏的某个局面必胜,当且仅当该局面对应节点的SG函数值大于0; 有向图游戏的某个局面必败,当且仅当该局面对应节点的SG函数值等于0。
mex运算 设S表示一个非负整数集合。定义 m e x ( S ) mex(S) mex(S)为求出不属于集合S的最小非负整数的运算: m e x ( S ) = m i n mex(S)=min mex(S)=min ( x ) ( x (x)(x (x)(x ∈ \in ∈ N , x ,x ,x ∉ \notin ∈/ S)
SG函数 在有向图游戏中,对于每个节点x,设从x出发共有k条有向边,分别到达节点
y
1
,
y
2
,
.
.
.
y
k
y_1,y_2,...y_k
y1,y2,...yk,定义SG(x)为x的后继节点
y
1
,
y
2
,
.
.
.
y
k
y_1,y_2,...y_k
y1,y2,...yk的SG函数值构成的集合再执行mex运算的结果,即:
S
G
(
x
)
=
m
e
x
(
{
S
G
(
y
1
)
,
S
G
(
y
2
)
,
.
.
.
,
S
G
(
y
k
)
}
)
SG(x)=mex(\{SG(y_1),SG(y_2),...,SG(y_k)\})
SG(x)=mex({SG(y1),SG(y2),...,SG(yk)})
特别地,整个有向图游戏G的SG函数值被定义为有向图游戏起点start的SG函数值。
有向图游戏的和 有向图游戏的和的SG函数值等于它包含的各个子游戏SG函数值的异或和。 S G ( G ) = S G ( G 1 ) x o r S G ( G 2 ) x o r S G ( G 3 ) x o r . . . x o r S G ( G m ) SG(G)=SG(G_1) xor SG(G_2) xor SG(G_3) xor...xor SG(G_m) SG(G)=SG(G1)xorSG(G2)xorSG(G3)xor...xorSG(Gm)
#include<bits/stdc++.h>
using namespace std;
int n,x,ans;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>x;
ans^=x;
}
if(ans) cout<<"Yes";
else cout<<"No";
}
因为第一阶拿到地面要拿一次,第二阶拿两次,第三阶拿三次…所以可以看成第二阶有两堆石子,第三阶有三堆…偶数阶石子为偶数堆,异或为0;奇数阶异或后就是原本石子数目,所以可以把原本所有奇数阶的石子进行异或,得到的就是答案。
#include<bits/stdc++.h>
using namespace std;
int n,x,ans;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>x;
if(i%2) ans^=x;
}
if(ans) cout<<"Yes";
else cout<<"No";
}
#include<bits/stdc++.h>
using namespace std;
int mem[10010],n,k,a[110],ans,box[10010][110];
int sg(int x)
{
if(mem[x]!=-1) return mem[x];
for(int i=1;i<=k;i++)
if(a[i]<=x) box[x][sg(x-a[i])]=1;//枚举集合内的数
for(int i=0;;i++)
if(!box[x][i]) return mem[x]=i;
}
int main()
{
cin>>k;
memset(mem,-1,sizeof mem);
for(int i=1;i<=k;i++)
cin>>a[i];
cin>>n;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
ans^=sg(x);
}
if(ans) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
return 0;
}
#include<bits/stdc++.h>
using namespace std;
int n,k,a[110],ans,maxn,mem[110],box[110][200];
int sg(int x)
{
if(mem[x]!=-1) return mem[x];
for(int i=0;i<x;i++)
for(int j=0;j<=i;j++)//枚举更小的两个堆
box[x][sg(i)^sg(j)]=1;
for(int i=0;;i++)
if(!box[x][i]) return mem[x]=i;
}
int main()
{
cin>>n;
memset(mem,-1,sizeof mem);
for(int i=1;i<=n;i++)
{
cin>>a[i];
ans^=sg(a[i]);
}
if(ans) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
return 0;
}
就是建有向图了。
#include<bits/stdc++.h>
using namespace std;
const int M=6010,N=2010;
int n,m,k,tot,ans,mem[N],box[N][2*N];
int head[N],ver[M],nex[M];
void add(int x,int y)
{
ver[++tot]=y,nex[tot]=head[x],head[x]=tot;
}
int SG(int u)
{
if (mem[u]!=-1) return mem[u];值已经被计算过了,直接返回
set<int> S;
for (int i=head[u];i;i=nex[i])
S.insert(SG(ver[i]));
for (int i=0;;i++)
if (!S.count(i))
return mem[u]=i;
}
int main()
{
cin>>n>>m>>k;
memset(mem,-1,sizeof mem);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
}
for(int i=1;i<=k;i++)
{
int x;
scanf("%d",&x);
/*for(int j=head[i];j;j=nex[j])
cout<<ver[j]<<' ';
cout<<endl;*/
ans^=SG(x);
//cout<<"x:"<<x<<" sg(x):"<<sg(x)<<endl;
}
if(ans) cout<<"win";
else cout<<"lose";
return 0;
}
状态压缩,在SG内转移状态
#include<bits/stdc++.h>
using namespace std;
int T,n,m,x;
int mem[1<<20];
int SG(int state)
{
if(mem[state]!=-1) return mem[state];
set<int>S;
for(int i=0;i<20;i++)
{
if((state>>i)&1)
{
for(int j=i-1;j>=0;j--)
{
if(!((state>>j)&1))
{
S.insert(SG(state^(1<<i)^(1<<j)));
break;
}
}
}
}
for(int i=0;;i++)
if(S.count(i)==0)
{
//printf("SG(%d):%d\n",state,i);
return mem[state]=i;
}
}
int main()
{
memset(mem,-1,sizeof mem);
for(int i=1;i<=20;i++)
mem[(1<<i)-1]=0;
cin>>T;
while(T--)
{
cin>>n;
int ans=0;
while(n--)
{
cin>>m;
int state=0;
for(int i=0;i<m;i++)
{
scanf("%d",&x);
state|=(1<<(20-x));
}
ans^=SG(state);
}
if(ans) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
return 0;
}
上面的有点慢,开氧气能卡过。试图预处理一下,结果MLE了。。。
#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
using namespace std;
int T,n,m,x;
int mem[1<<20],nex[21],a[21],num[1<<20];
int SG(int state)
{
if(mem[state]!=-1) return mem[state];
set<int>S;
int now=state,last=0;
while(now)
{
int t=lowbit(now);
if(nex[num[t]]!=-1)
S.insert(SG(state^t^(1<<nex[num[t]])));
now-=t;
}
/*for(int i=0;i<20;i++)
{
if((state>>i)&1)
{
for(int j=i-1;j>=0;j--)
{
if(!((state>>j)&1))
{
S.insert(SG(state^(1<<i)^(1<<j)));
break;
}
}
}
}*/
for(int i=0;;i++)
if(S.count(i)==0)
{
//printf("SG(%d):%d\n",state,i);
return mem[state]=i;
}
}
int main()
{
memset(mem,-1,sizeof mem);
for(int i=1;i<=20;i++)
mem[(1<<i)-1]=0,num[1<<(i-1)]=(i-1);
cin>>T;
while(T--)
{
cin>>n;
int ans=0;
while(n--)
{
cin>>m;
int state=0;
for(int i=0;i<m;i++)
{
scanf("%d",&a[i]);
a[i]=20-a[i];
state|=(1<<(a[i]));
}
nex[m-1]=a[m-1]-1;
for(int i=m-2;i>=0;i--)
{
if(a[i]==a[i+1]+1) nex[i]=nex[i+1];
else nex[i]=a[i]-1;
}//预处理出下一个位置
ans^=SG(state);
}
if(ans) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
return 0;
}
不好想的是当作台阶Nim处理QAQ:
#include<bits/stdc++.h>
using namespace std;
int T,n;
int vis[25];
int main()
{
scanf("%d",&T);
while(T--)
{
int ans=0;
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
int now=0,num;
memset(vis,0,sizeof(vis));
scanf("%d",&num);
for(int j=1;j<=num;++j)
{
int x;
scanf("%d",&x);
vis[x]=1;
}
int cnt=0,tot=0;
for(int j=20;j>=0;j--)
{
if(!vis[j])
{
if(tot)
{
ans^=tot;
tot=0;
}
cnt^=1;
}
else if(cnt) ++tot;
}
if(tot) ans^=tot;
}
printf("%s\n",ans?"YES":"NO");
}
return 0;
}
另外扔一些最近写的博弈
#include<iostream>
#include<algorithm>
using namespace std;
int n,m,ans,a[10001];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
a[i]*=2;
}
for(int i=1;i<=m;i++)
{
int x,y,z;
cin>>x>>y>>z;
a[x]+=z,a[y]+=z;
}
sort(a+1,a+1+n);
for(int i=n;i>=1;i-=2)
ans+=a[i]-a[i-1];
cout<<ans/2;
return 0;
}
首先,“一眼能看出”
if(m<=n) cout<<"Alice"<<endl;
if(m>n&&(m-n)%(s+1)==0) cout<<"Alice"<<endl;
考虑当m>n&&(m-n)%(s+1)!=0
时,Alice是否有办法用一步变成后手必胜局面。可以斩三堆使得n<s,这样只用考虑m堆的情况(因为从n取不会改变最终到m堆的先后手情况,如果Bob进一步斩三堆也是稳输),假设只剩下m堆了,Alice后手,这在m%(s+1)==0的情况下Alice稳赢。所以Alice可以在第一步的时候就通过调整,使得调整后呈现后手必胜局面。
Bob必胜态大小为
n
n
n的堆Alice取一个Bob取另一个,大小为
m
m
m的堆Bob取
s
+
1
−
s+1-
s+1−Alice取的数量。
否则Alice能一步变成后手必胜态(
n
>
s
n>s
n>s时同时取3个堆,否则取
m
m
m堆)。
然后这个题目有点坑,输入顺序应该是m,n,s…
#include<bits/stdc++.h>
using namespace std;
int main()
{
int s,m,n;
while(~scanf("%d%d%d",&m,&n,&s))
{
if(n<=s&&m%(s+1)==0) cout<<"Bob"<<endl;
else cout<<"Alice"<<endl;
}
}