巴什博奕
只有一堆n个物品,两个人轮流从这堆物品中取物, 规定每次至少取一个,最多取m个。最后取光者得胜。
分析
(1)当n≤m时,由于一次最少拿1个、最多拿m个,甲可以一次拿完,先手赢。
(2)当n=m+1时,无论甲拿走多少个(1~m个),剩下的都多于1个、少于等于m 个,乙都能一次拿走剩余的石子,后手取胜。
上面两种情况可以扩展为以下两种情况:
A.如果n%(m+1)=0,即n是m+1的整数倍,那么不管甲拿多少,例如k个,乙都 拿m+1-k个,使得剩下的永远是m+1的整数倍,直到最后的m+1个,所以后拿 的乙一定赢。
B.如果n%(m+1)!=0,即n不是m+1的整数倍,还有余数r,那么甲拿走r个,剩下的是 m+1的倍数,这样就转移到了情况(A),相当于甲、乙互换,结果是甲赢。
例题:
题意:
在一个m*n的棋盘内,从(1,m)点出发,每次可以进行的移动是:左移一,下移一,左下移一。然后kiki每次先走,判断kiki时候会赢(对方无路可走的时候)。
分析:
我们可以把PN状态的点描绘出来:
可以发现 n,m 中有一个是2 的倍数,则 为先手获胜,反之,后手必胜。
code:
1 #include<bits/stdc++.h> 2 using namespace std; 3 int main( ) 4 { 5 int n,m; 6 while(scanf("%d%d",&n,&m)&&(n!=0||m!=0)) 7 { 8 if(n%2==0||m%2==0) 9 printf("Wonderful! "); 10 else 11 printf("What a pity! "); 12 } 13 return 0; 14 }
NIM 游戏
通常的Nim游戏的定义是这样的:有若干堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时所有的石子堆都已经被拿空了,则判负(因为他此刻没有任何合法的移动)。
定义P-position和N-position,其中P代表Previous,N代表Next。直观的说,上一次move的人有必胜策略的局面是P-position,也就是“后手可保证必胜”或者“先手必败”,现在轮到move的人有必胜策略的局面是N-position,也就是“先手可保证必胜”。
(Bouton's Theorem):对于一个Nim游戏的局面(a1,a2,...,an),它是P-position当且仅当a1^a2^...^an=0,其中^表示异或(xor)运算。
对于某个局面(a1,a2,...,an),若a1^a2^...^an!=0,一定存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。
对于某个局面(a1,a2,...,an),若a1^a2^...^an=0,一定不存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。
SG 函数
必胜点和必败点的概念:
Sprague-Grundy定理(SG定理):
游戏和的SG函数等于各个游戏SG函数的Nim和。这样就可以将每一个子游戏分而治之,从而简化了问题。而Bouton定理就是Sprague-Grundy定理在Nim游戏中的直接应用,因为单堆的Nim游戏 SG函数满足 SG(x) = x。
SG函数:
首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
对于任意状态 x , 定义 SG(x) = mex(S),其中 S 是 x 后继状态的SG函数值的集合。如 x 有三个后继状态分别为 SG(a),SG(b),SG(c),那么SG(x) = mex{SG(a),SG(b),SG(c)}。 这样 集合S 的终态必然是空集,所以SG函数的终态为 SG(x) = 0,当且仅当 x 为必败点P时。
计算1~n的SG函数值步骤如下:
1、使用 数组f 将 可改变当前状态 的方式记录下来。
2、然后我们使用 另一个数组 将当前状态x 的后继状态标记。
3、最后模拟mex运算,也就是我们在标记值中 搜索 未被标记值 的最小值,将其赋值给SG(x)。
4、我们不断的重复 2 - 3 的步骤,就完成了 计算1~n 的函数值。
例题: hdu 1850
思路:1)如若给出 的是必败状态:a1^a2^......^an=0,则先手不会有任何可能获得胜利;
2)若给出的是必胜状态:a1^a2^.......^an=k,(其中k不为零),那么我们的目的是要把必胜状态
转化为必败状态从 而使得先手胜利。若a1^a2^...^an!=0,一定存在某个合法的移动,将ai
改变成ai'后满足a1^a2^...^ai'^...^an=0。若a1^a2^...^an=k,则一定存在某个ai,
它的二进制 表示在k的最高位上是1(否则k的最高位那个1是怎么得到的)。这时ai^k<ai一定
成立。则我们可以将ai改变成ai'=ai^k,此时a1^a2^...^ai'^...^an=a1^a2^...^an^k=0。
code:
1 #include<stdio.h> 2 int main() 3 { 4 int x,m,a[110],i,ans; 5 while(scanf("%d",&m),m) 6 { 7 x=ans=0; 8 for(i=0;i<m;i++) 9 { 10 scanf("%d",&a[i]); 11 x^=a[i]; 12 } 13 for(i=0;i<m;i++) 14 ans+=(a[i]>(x^a[i])); 15 printf("%d ",ans); 16 } 17 return 0; 18 }
sg函数的简单应用
code:
1 #include<cstdio> 2 #include<string.h> 3 using namespace std; 4 int fib[20],sg[1010],m,n,p; 5 int calsg(int now){ 6 int i,tem; 7 int next[20]; 8 memset(next,0,sizeof(next)); 9 for(i=1;fib[i]<=now;i++){ 10 tem=now-fib[i]; 11 if(sg[tem]==-1) 12 sg[tem]=calsg(tem); 13 next[sg[tem]]=1; 14 } 15 for(i=0;;i++) 16 if(next[i]==0){ 17 return i; 18 } 19 } 20 int main(){ 21 int i,j,tem; 22 fib[1]=1; 23 fib[2]=2; 24 for(i=3;i<=16;i++) 25 fib[i]=fib[i-1]+fib[i-2]; 26 memset(sg,-1,sizeof(sg)); 27 while(scanf("%d %d %d",&m,&n,&p)==3 && !(m==0 && n==0 && p==0)){ 28 tem=calsg(m)^calsg(n)^calsg(p); 29 if(tem==0) 30 printf("Nacci "); 31 else 32 printf("Fibo "); 33 }