Nim博弈
Nim
nim结论:对于一个局面,当且仅当a[1] xor a[2] xor ...xor a[n]=0时,该局面为P局面,即必败局面。(对于取任意个数SG(x)=x,也符合SG定理).
- 结论变换:把n堆石子的石子数用二进制表示,统计每个二进制位上1的个数,若每一位上1的个数mod(2)全部为0,则必败,否则必胜。
anti-nim(反nim游戏)
正常的nim游戏是取走最后一颗的人获胜,而反nim游戏是取走最后一颗的人输。
一个状态为必胜态,当且仅当:
- 1)所有堆的石子个数为1,且NIM_sum(xor)=0
- 2)至少有一堆的石子个数大于1,且 NIM_sum(xor)≠0
#include<bits/stdc++.h> using namespace std; const int maxn=2e5+10; int main() { int n,m; scanf("%d",&m); for( int i=1;i<=m;i++ ) { scanf("%d",&n); int ans=0; int flag=0; for( int j=1;j<=n;j++ ) { int x;scanf("%d",&x); if( x>1 ) flag=1; ans^=x; } if( !flag && !ans ) puts("John"); else if( flag && ans ) puts("John"); else puts("Brother"); } }
Moore’s Nim:
模型:n堆石子,每次从不超过k堆中取任意多个石子,最后不能取的人失败。
这是一个nim游戏的变形,也是有结论:把n堆石子的石子数用二进制表示,统计每个二进制位上1的个数,若每一位上1的个数mod(k+1)全部为0,则必败,否则必胜。
n堆石子,每次从不超过k堆中取任意多个石子,最后不能取的人失败。
这是一个nim游戏的变形,也是有结论:
- 把n堆石子的石子数用二进制表示,统计每个二进制位上1的个数,若每一位上1的个数mod(k+1)全部为0,则必败,否则必胜。
- 该结论可以类比普通的Nim博弈(理解),Nim博弈每次对一堆操作,那么判断每一位二进制位个数是否mod(2)全为0。
#include<bits/stdc++.h> using namespace std; const int maxn=2e5+10; int a[32]; int main() { int t;scanf("%d",&t); while( t-- ) { int n;scanf("%d",&n); for( int i=0;i<30;i++ ) a[i]=0; for( int i=1;i<=n;i++ ) { int x;scanf("%d",&x); int cnt=0; while( x ) { if( x&1 ) a[cnt]++; cnt++; x>>=1; } } bool flag=true; for( int i=0;i<30;i++ ) if( a[i]%4 ) flag=false; if( !flag ) puts("Yes"); else puts("No"); } }
Mis`ere Moore’s Nimk
其他的条件一样,只是谁拿走最后一个谁输。
先手必胜:
- 1.石子规模都为1,且堆数mod (m + 1) != 1
- 2.石子规模不全为1,且当堆数以2进制表示时,存在某一二进制位上1的和mod(m + 1) != 0
鸽~
Staircase NIM
顾名思义就是在阶梯上进行,有若干个石子放在阶梯上,每次可以选择任意层的任意个石子将其移动到该层的下一层。最后不能操作的人输。
解法: 对奇数堆进行Nim博弈,因为我们根据必胜策略将奇数堆的石子移动到偶数堆(类比于从一堆中拿走任意石子),如果对手将该堆石子移动的下一个奇数堆,我们只需要将这堆石子继续移动到偶数堆。如果对手操作其他奇数堆的石子,那么就是奇数堆石子的Nim博弈。
该题石子数的转化是两堆石子的位置差值。
- 对与相近的两个棋子,将其看做石子数为2个棋子之间的空隙的石子堆。设想当前是必胜态,如果对手移动右棋子,那和取石子无异,如果是移动左棋子呢?我们可以相应地移动右棋子,使空隙数不变,这样仍然回到了必胜的状态。
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; const int maxn=2e5+10; int m,n; int a[maxn]; int main() { scanf("%d",&m); for( int i=1;i<=m;i++ ) { scanf("%d",&n); int ans=0; for( int j=1;j<=n;j++ ) scanf("%d",&a[j]); if( n&1 ) a[++n]=0; sort(a+1,a+1+n); for( int i=2;i<=n;i+=2 ) { ans^=a[i]-a[i-1]-1; } if( ans ) puts("Georgia will win"); else puts("Bob will win"); } }
HDU1730
题意:如图所示,游戏在一个n行m列的棋盘上进行,每行有一个黑子(黑方)和一个白子(白方)。执黑的一方先行,每次玩家可以移动己方的任何一枚棋子到同一行的任何一个空格上,当然这过程中不许越过该行的敌方棋子。双方轮流移动,直到某一方无法行动为止,移动最后一步的玩家获胜。
分析:每一行中加的距离可以看作是石子的堆数,因为每一个当一个人往两侧走的时候,另一个人可以往那个方向走直至距离一样,这个基础上它还可以再走,所以中间的距离只会减少不会增加,就可以看作是一个多堆石子的Nim游戏。
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstdlib> #include<cstring> using namespace std; const int maxn=4e3+10; int main() { int n,m; while( cin>>n>>m ) { int a,b,ans=0; while( n-- ) { cin>>a>>b; ans^=(abs(a-b)-1); } if( ans ) puts("I WIN!"); else puts("BAD LUCK!"); } }
新Nim游戏:
在第一个回合中,第一个游戏者可以直接拿走若干个整堆的火柴。可以一堆都不拿,但不可以全部拿走。第二回合也一样,第二个游戏者也有这样一次机会。从第三个回合(又轮到第一个游戏者)开始,规则和Nim游戏一样。如果你先拿,怎样才能保证获胜?如果可以获胜的话,还要让第一回合拿的火柴总数尽量小。
解法:
为使后手必败,先手留给后手的必然是若干线性无关的数字,否则后手可以留下一个异或和为零的非空子集使得先手必败,故问题转化为拿走和最小的数字使得留下的数线性无关,即留下和最大的线性基,这样拿走的数量显然最少,找到和最大的线性基只需贪心的把数字从大到小加入到基中即可(证明需用到拟阵)抱歉只会cv结论
#include<bits/stdc++.h> using namespace std; typedef long long ll; ll sum; int k,num[520],d[520]; inline int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();} while(ch>='0'&&ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } int Insert(int k) { for(int i=31;i>=0;--i) { if(k&(1<<i)) { if(!d[i]) {d[i]=k; return 1;} else k^=d[i]; } } return 0; } bool cmp(int a,int b) {return a>b;} int main() { k=read();sum=0; for(int i=1;i<=k;++i) num[i]=read(); sort(num+1,num+1+k,cmp); for(int i=1;i<=k;++i) if(!Insert(num[i])) sum+=num[i]*1ll; printf("%lld\n",sum); return 0; }