数学—博弈
1.总述
P-position为必败态,N-position为必胜态 能到必败态的状态为必胜态,只能到必胜态的状态为必败态。
做题思路:①手动或机器打表找规律 ②打SG表(ICG类型)③记忆化搜索(状态数有限)
注意:不要抱有记忆化搜索会少搜一些状态的侥幸心理
2.巴什博弈
问题模型:只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个,最后取光者得胜。
模型归纳:一堆n个物品,每次取一堆中的有限个,游戏次数有限。
思路:设出最后的状态为N态或P态,由N、P态之间转换关系顺推或倒推,最后求出所求状态。
例题:
1.悼念512汶川大地震遇难同胞——选拔志愿者 HDU - 2188
#include<bits/stdc++.h>
using namespace std;
/*
由题意可设n为P态,则n-1,n-2,...,n-m均为N态,n-m-1为P态
由此可观察出,当 n是m+1的倍数时,则可推出0是P态,否则为N态
*/
int main()
{
int Case;
scanf("%d",&Case);
for(int o=1;o<=Case;o++)
{
int n,m;
scanf("%d%d",&n,&m);
if(n%(m+1)==0)printf("Rabbit\n");
else printf("Grass\n");
}
return 0;
}
3.威佐夫博弈
问题模型:有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
模型归纳:两堆若干个物品,每次取一堆中的若干个或两堆中的相同个,游戏次数有限。
思路:奇异状态必败,否则必胜。
关于奇异状态:https://blog.csdn.net/qq_41311604/article/details/79980882
检验威佐夫博弈模型的一个小程序:
1 #include<bits/stdc++.h>
2 using namespace std;
3 //利用n^2的时间可以初始化出前n组奇异局势
4 int Mark[100005]={0};
5 int main()
6 {
7 for(int i=1;i<=100;i++)
8 {
9 int tmp;
10 for(int j=0;j<=i*2;j++)
11 {
12 if(!Mark[j])
13 {
14 tmp=j;
15 Mark[j]=1;
16 break;
17 }
18 }
19 Mark[tmp+i-1]=1;
20 printf("第%d个奇异局势:a为%d b为%d ",i,tmp,tmp+i-1);
21 printf("a的1.618倍为%.5lf 差值的1.618倍为%.5lf\n",(sqrt(5)+1)/2*tmp,(sqrt(5)+1)/2*(i-1));
22 }
23 return 0;
24 }
例题:
1.取石子游戏 HDU - 1527
1 #include<bits/stdc++.h>
2
3 using namespace std;
4
5 //如果差值的1.618倍(取下整)等于a则为奇异局势
6
7 //理解:我们已知奇异局势差值的1.618倍再取下整为a,设其为c,那么当a、b同时加减,c与a一定会产生差值
8
9 //因此,非奇异局势一定不会有这种性质,即这个性质是充要的
10
11 //同理,根据黄金分割的性质,a的1.618倍还等于b(但要取上整)
12
13 int main()
14
15 {
16
17 int a,b;
18
19 while(scanf("%d%d",&a,&b)!=EOF)
20
21 {
22
23 if(a>b)swap(a,b);
24
25 //利用差值的1.618倍:
26
27 //if(a==((int)((b-a)*(sqrt(5)+1)/2)))printf("0\n");
28
29 //else printf("1\n");
30
31 //利用a的1.618倍:
32
33 if(((int)((sqrt(5)+1)*a/2))+1==b)printf("0\n");
34
35 else printf("1\n");
36
37 }
38
39 return 0;
40
41 }
4.尼姆博弈
问题模型:有若干堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时所有的石子堆都已经被拿空了,则判负(因为他此刻没有任何合法的移动)。
模型归纳:若干堆若干个物品,每次取一堆中的若干个,游戏次数有限
思路:所有游戏状态的异或为0为必败态,否则为必胜态
证明(对总述中的三个命题):
- 第一个命题显然,terminal position只有一个,就是全0,异或仍然是0。
- 第二个命题,对于某个局面(a1,a2,...,an),若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。
- 第三个命题,对于某个局面(a1,a2,...,an),若a1^a2^...^an=0,一定不存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。因为异或运算满足消去率,由a1^a2^...^an=a1^a2^...^ai'^...^an可以得到ai=ai'。所以将ai改变成ai'不是一个合法的移动。证毕。
例题:
1.Rabbit and Grass
1 #include<bits/stdc++.h> 2 using namespace std; 3 int main() 4 { 5 int m; 6 while(1) 7 { 8 scanf("%d",&m); 9 if(m==0)break; 10 int tmp1,tmp2=0; 11 while(m--) 12 { 13 scanf("%d",&tmp1); 14 tmp2^=tmp1; 15 } 16 if(tmp2)puts("Rabbit Win!"); 17 else puts("Grass Win!"); 18 } 19 return 0; 20 }
5.Fibonacci博弈
问题模型:
有一堆个数为n的石子,游戏双方轮流取石子,满足:
- (1)先手不能在第一次把所有的石子取完;
- (2)之后每次可以取的石子数介于1到对手刚取的石子数的2倍之间(包含1和对手刚取的石子数的2倍)。
- 约定取走最后一个石子的人为赢家。
模型归纳:一堆若干个物品,每次取区间内的个数,游戏次数有限
思路:当n为Fibonacci数时,先手必败。即存在先手的必败态当且仅当石头个数为Fibonacci数。(打表找规律)
证明:https://blog.csdn.net/dgq8211/article/details/7602807
6.公平组合博弈(Impartial Combinatorial Games - ICG)
问题模型:
- 定义:
- (1)两人参与。
- (2)游戏局面的状态集合是有限。
- (3)对于同一个局面,两个游戏者的可操作集合完全相同
- (4)游戏者轮流进行游戏。
- (5)当无法进行操作时游戏结束,此时不能进行操作的一方算输。
- (6)无论游戏如何进行,总可以在有限步数之内结束。
模型归纳:若干个游戏若干种状态,每次改变一个游戏的状态,游戏次数有限
思路:
- 将游戏对应一个有向无环图
- mex运算:取出不存在于目标集合中的最小非负整数
- Sprague-Garundy函数g:g(x)=mex{ g(y) | y是x的后继 }
- 将SG值看成Nim中的石子堆,则ICG的必胜策略对应Nim的必胜策略
得到sg函数的模板:
1 void Getsg(int n)//n±íʾ×ÓÓÎÏ·"ʯ×Ó"¸öÊýÉÏÏÞ 2 { 3 memset(sg,0,sizeof(sg)); 4 for(int i=1;i<=n;i++) 5 { 6 memset(Mark,0,sizeof(Mark)); 7 for(int j=1;f[j]<=i;j++)Mark[sg[i-f[j]]]=1;//f[i]ΪµÚiÖÖÄÜ×ߵIJ½Êý 8 for(int j=0;j<=n;j++)//ÇóÄܵ½´ïµÄ״̬ÖеÄ×îС·Ç¸ºÕûÊý̬ 9 { 10 if(!Mark[j]) 11 { 12 sg[i]=j; 13 break; 14 } 15 } 16 } 17 }
例题:
1.Fibonacci again and again
因为是公平组合游戏,且要求的只是判断状态,所以直接打sg表就好
1 #include<bits/stdc++.h> 2 using namespace std; 3 int f[20],Mark[1005],sg[1005]={0}; 4 void Init() 5 { 6 f[0]=1;f[1]=1; 7 for(int i=2;i<=16;i++)f[i]=f[i-1]+f[i-2];//µÃµ½ì³²¨ÄÇÆõÊýÁв»´óÓÚ1000µÄÏî 8 9 for(int i=1;i<=1000;i++)//µÃµ½sgº¯Êý 10 { 11 memset(Mark,0,sizeof(Mark)); 12 for(int j=1;f[j]<=i;j++)Mark[sg[i-f[j]]]=1; 13 for(int j=0;j<=1000;j++)//ÇóÄܵ½´ïµÄ״̬ÖеÄ×îС·Ç¸ºÕûÊý̬ 14 { 15 if(!Mark[j]) 16 { 17 sg[i]=j; 18 break; 19 } 20 } 21 } 22 } 23 void Solve() 24 { 25 int n,m,p; 26 while(cin>>n>>m>>p) 27 { 28 if(n==0&&m==0&&p==0)break; 29 if(sg[n]^sg[m]^sg[p])puts("Fibo"); 30 else puts("Nacci"); 31 } 32 } 33 int main() 34 { 35 Init(); 36 Solve(); 37 return 0; 38 }
7.反尼姆博弈,反公平组合博弈
问题模型:全为0为胜态,其余与之前内容相同
模型归纳:
- 先手必胜当且仅当:
- ( 1)所有堆的石子数都为 1 且游戏的 SG 值为 0;
- ( 2)有些堆的石子数大于 1 且游戏的 SG 值不为 0。
- 或者
- (1)游戏的 SG 函数不为 0 且游戏中某个单一游戏的 SG 函数大于 1;
- (2)游戏的 SG 函数为 0 且游戏中没有单一游戏的 SG 函数大于 1。
题目
1.kiki's game HDU - 2147
手动画表找规律:
1 #include<bits/stdc++.h> 2 /* 3 思路:画图找规律,发现行数和列数均为奇数的话必败 4 */ 5 int main() 6 { 7 int n,m; 8 while(1) 9 { 10 scanf("%d%d",&n,&m); 11 if(n==0&&m==0)break; 12 if(n&1 && m&1)puts("What a pity!"); 13 else puts("Wonderful!"); 14 } 15 return 0; 16 }
2.Good Luck in CET-4 Everybody! HDU - 1847
a.记忆化搜索:
1 #include<bits/stdc++.h> 2 using namespace std; 3 /* 4 思路:根据博弈原理递推即可,此代码用记忆化搜索实现 5 注意:1.用什么分别表示没有访问过、负态、胜态 6 时间复杂度:O(1000)空间复杂度:O(1000) 7 */ 8 int f[1005]={0}; 9 int dfs(int n) 10 { 11 if(f[n])return f[n]; 12 if(n==0)return f[n]=-1; 13 14 for(int i=0;n-(1<<i)>=0;i++) 15 { 16 if(dfs(n-(1<<i))==-1)return f[n]=1; 17 } 18 return f[n]=-1; 19 } 20 int main() 21 { 22 int n; 23 while(scanf("%d",&n)!=EOF) 24 { 25 if(dfs(n)==1)puts("Kiki"); 26 else puts("Cici"); 27 } 28 return 0; 29 }
b.转化为二进制(错误的做法,因为4既可以看作100也可以看作10+10)
3.Being a Good Boy in Spring Festival
比较新的一道题,求的是一般的nim游戏,先手必胜的第一步走法的方案数
考察的是对nim博弈模型的理解
考虑由必胜态到必败态的转移时,我们是找一个数变小,然后让异或和为0,为此只需要先求出原来的异或和,找到异或和的最高位是哪一位,那么有这一位的子游戏就是能够作为必胜到必败转移策略的子游戏(且只有这种情况),证明:
- 有这一位的话可以把这一位取反,然后较小的位也对应取反,这样可以使新异或和为0并且这样得到的新子游戏是较小的
- 若没有这一位的话,为了消掉异或和中的这一位,就必须向高位借位,这样会使异或和产生新的高位,证毕
1 #include<bits/stdc++.h> 2 using namespace std; 3 int main() 4 { 5 int n,Xor,ans,pos,a[105]={0}; 6 Ret: 7 Xor=0,ans=0,pos=-1; 8 scanf("%d",&n); 9 if(n==0)goto End; 10 for(int i=1;i<=n;i++)scanf("%d",&a[i]),Xor^=a[i]; 11 for(int i=20;i>=0;i--)if((1<<i)&Xor){pos=i;break;} 12 if(pos!=-1) 13 for(int i=1;i<=n;i++)if(a[i]&(1<<pos))ans++; 14 cout<<ans<<endl; 15 goto Ret; 16 End: 17 return 0; 18 } 19 /* 20 1 21 10 22 11 3 23 100 24 101 5 25 110 26 111 7 27 1000 28 1001 9 29 1010 30 1011 11 31 */
4.Digital Deletions
不属于归纳出的任何一种模型,且状态数较少(100w),因此考虑用记忆化搜索
5.萌萌的糖果博弈