今天好好反省自己的博弈方面的欠缺。这个博客 讲解博弈比较全面。
(一)巴什博奕(Bash Game):只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。最后取光者得胜。 ans:保持给对手留下(m+1)的倍数,就能最后获胜。
巴什博奕例题: HDU 1846/2188
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <iostream> #include <stdio.h> int main () { int t; scanf ("%d" ,&t); while (t--) { int n,m; scanf ("%d%d" ,&n,&m); puts (n%(m+1 )?"first" :"second" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <iostream> #include <stdio.h> int main () { int t; scanf ("%d" ,&t); while (t--) { int n,m; scanf ("%d%d" ,&n,&m); puts (n%(m+1 )?"Grass" :"Rabbit" ); } }
hdu 2149
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> #include <stdio.h> int main () { int n,m; while (~scanf ("%d%d" ,&m,&n)) { if (m%(n+1 )==0 &&n<m)puts ("none" ); else { if (n>=m) for (int i=m;i<=n;i++) printf ("%d%c" ,i," \n" [i==n]); else printf ("%d\n" ,m%(n+1 )); } } }
变相巴什博奕: HDU 1517/POJ 2505 起点:1 操作:乘 [2-9]中的一个数 情况分界:1-9 -9X2 - 9X2X9 …….. 先者掌握奇数局面 ,后者掌握偶数局面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> #include <stdio.h> #include <cstring> using namespace std ;int main () { int n; while (~scanf ("%d" ,&n)) { int c=1 ; while (c<n) c*=18 ; c/=18 ; if (c*9 <n)puts ("Ollie wins." ); else puts ("Stan wins." ); } }
(二)威佐夫博奕(Wythoff Game):有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。 ans:面对非奇异局势,先拿者必胜;反之,则后拿者取胜。 奇异局势:ak =[k(1+√5)/2],bk= ak + k (k=0,1,2,…,n 方括号表示取整函数)
HDU 1527
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> #include <stdio.h> double k=(1 +sqrt (5.0 ))/2 ;int main () { int n,m; while (~scanf ("%d%d" ,&n,&m)) { if (n>m)swap(n,m); int ans=(int )(k*(m-n)); if (n==ans)puts ("0" ); else puts ("1" ); } }
(三)尼姆博奕(Nimm Game):有三堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。 ans: 取火柴的游戏 题目1:今有若干堆火柴,两人依次从中拿取,规定每次只能从一堆中取若干根, 可将一堆全取走,但不可不取,最后取完者为胜,求必胜的方法。 题目2:今有若干堆火柴,两人依次从中拿取,规定每次只能从一堆中取若干根, 可将一堆全取走,但不可不取,最后取完者为负,求必胜的方法。 题目1例题(Nim博弈): HDU 1849
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> #include <stdio.h> int main () { int n; int tp; while (scanf ("%d" ,&n),n) { int ans=0 ; for (int i=0 ;i<n;i++) { scanf ("%d" ,&tp); ans^=tp; } puts (ans?"Rabbit Win!" :"Grass Win!" ); } }
求可胜操作数,用ans^a[i]得到除a[i]以外的其他值的亦或值,若该值比a[i]小,那么可另a[i]等于该值,使亦或总值等于0. HDU 1850
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> #include <stdio.h> int a[1000002 ];int main () { int n; while (~scanf ("%d" ,&n),n) { int ans=0 ,cnt=0 ; for (int i=0 ;i<n;i++) { scanf ("%d" ,a+i); ans^=a[i]; } if (ans) for (int i=0 ;i<n;i++) if ((a[i]^ans)<a[i])cnt++; printf ("%d\n" ,cnt); } }
hdu 2176(同1850)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <stdio.h> int a[200003 ];void getans (int n,int res) { for (int i=0 ;i<n;i++) if ((a[i]^res)<a[i])printf ("%d %d\n" ,a[i],a[i]^res); } int main () { int n; while (scanf ("%d" ,&n),n) { for (int i=0 ;i<n;i++) scanf ("%d" ,&a[i]); int res=0 ; for (int i=0 ;i<n;i++) res^=a[i]; if (res){puts ("Yes" );getans(n,res);} else puts ("No" ); } }
题目2例题 (反向Nim博弈): HDU 1907
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int main () { int t; scanf ("%d" ,&t); while (t--) { int n; int sum=0 ; int t1=0 ; scanf ("%d" ,&n); for (int i=0 ;i<n;i++) { int tp; scanf ("%d" ,&tp); if (tp==1 )t1++; sum^=tp; } if (sum==0 &&t1!=n||sum!=0 &&t1==n) puts ("Brother" ); else puts ("John" ); } }
hdu 2509
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int main () { int n; while (~scanf ("%d" ,&n)) { int tp; int sum=0 ; int s1=0 ; for (int i=0 ;i<n;i++) { scanf ("%d" ,&tp); sum^=tp; if (tp==1 )s1++; } if (sum==0 &&s1!=n||sum!=0 &&s1==n)puts ("No" ); else puts ("Yes" ); } }
S-Nim sg博弈+nim博弈
证明该解法的正确性: 对于每个队列的sg值k,显然至少从0——k-1的值在后继点中是可以找到的,那么对手选择这0——k-1个后继点,都将导致总亦或值不为0(即Nim博弈的模型),若选择大于k的后继点(如果有的话),当然也是自取灭亡。假如对手选取的点为p(p>k),那么新产生的所有队列亦或值肯定不为0,只是把修改的队列抬高了而已(对于新的队列,他的后继点至少可以取到1——p-1的所有值)
HDU 1536/1944
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <stdio.h> #include <cstring> #include <algorithm> #include <string.h> #include <iostream> #define mem(x,y) memset(x,y,sizeof(x)) #define inf 0x3f3f3f3f using namespace std ;int a[101 ],k;int mmr[10003 ];int sg (int n) { if (mmr[n]!=-1 )return mmr[n]; bool vis[101 ]={0 }; for (int i=0 ;i<k&&n-a[i]>=0 ;i++) vis[sg(n-a[i])]=1 ; for (int i=0 ;;i++) if (!vis[i])return mmr[n]=i; } int main () { while (scanf ("%d" ,&k),k) { for (int i=0 ;i<k;i++) scanf ("%d" ,a+i); sort(a,a+k); int t; scanf ("%d" ,&t); mem(mmr,-1 ); for (int i=0 ;i<t;i++) { int n; scanf ("%d" ,&n); int ans=0 ; for (int i=0 ;i<n;i++) { int l; scanf ("%d" ,&l); ans^=sg(l); } if (ans)printf ("W" ); else printf ("L" ); if (i==t-1 )puts ("" ); } } }
HDU 1848
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <stdio.h> #include <cstring> #include <algorithm> #include <string.h> #include <iostream> #define mem(x,y) memset(x,y,sizeof(x)) #define inf 0x3f3f3f3f using namespace std ;int fb[300 ];void gtfb () { int i; fb[1 ]=1 ,fb[2 ]=2 ; for (i=3 ;fb[i-1 ]<=1000 ;i++) fb[i]=fb[i-1 ]+fb[i-2 ]; } int sg[1004 ];int gtsg (int n) { if (sg[n]!=-1 )return sg[n]; bool vis[100 ]={0 }; for (int i=1 ;n-fb[i]>=0 ;i++) vis[gtsg(n-fb[i])]=1 ; for (int i=0 ;;i++) if (!vis[i])return sg[n]=i; } int main () { gtfb(); mem(sg,-1 ); sg[0 ]=0 ; int a[3 ]; while (scanf ("%d%d%d" ,&a[0 ],&a[1 ],&a[2 ]),a[0 ]) { int ans=0 ; for (int i=0 ;i<3 ;i++) ans^=gtsg(a[i]); puts (ans?"Fibo" :"Nacci" ); } }
那么SG博弈是个啥? 对于一个博弈问题,它的下一状态(后继点)只能推到有限个子状态中,那么它就是满足SG博弈的。所以Nim博弈就是一种特殊的SG博弈。 SG值:一个点的SG值就是一个不等于它的后继点的SG的且大于等于零的最小整数。 后继点:也就是按照题目要求的走法(比如取石子可以取的数量,方法)能够走一步达到的那个点。
sg (X)= min{n| n∈N ,n≠ for y∈F(x)} 就是指x状态下做出决策不能达到的最小状态, sg(x)=0时为必败态. 性质: (1)对于任意的局面,如果它的SG 值为0,那么它的任何一个后继局面的SG 值不为0; (2)对于任意的局面,如果它的SG 值不为0,那么它一定有一个后继局面的SG 值为0。
例题 HDU 1847:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <stdio.h> #include <cstring> #include <algorithm> #include <string.h> #include <iostream> #define mem(x,y) memset(x,y,sizeof(x)) #define inf 0x3f3f3f3f using namespace std ;int sg[1003 ];int getsg (int n) { if (sg[n]!=-1 )return sg[n]; bool vis[100 ]={0 }; for (int i=1 ;i<=n;i*=2 ) vis[getsg(n-i)]=1 ; for (int i=0 ;;i++) if (!vis[i])return sg[n]=i; } int main () { int n; mem(sg,-1 ); sg[0 ]=0 ; while (~scanf ("%d" ,&n)) puts (getsg(n)?"Kiki" :"Cici" ); }
HDU 1404 用dfs模拟推理过程是一个机智的博弈解决方法。 这个题说明,在不需要加类似Nim博弈的场合,sg值的作用只有两种,0和非0.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <cstdio> #include <cstring> #include <vector> #include <algorithm> #include <iostream> using namespace std ;#define maxn 1000002 int sg[maxn];int dfs (int n) { if (sg[n]!=-1 )return sg[n]; for (int i=1 ;i<=n;i*=10 ) { int t=n; if (n/i%10 ) { do { t-=i; if (t<i)break ; if (!dfs(t))return sg[n]=1 ; }while (t/i%10 ); } else if (!dfs(n/i/10 ))return sg[n]=1 ; } return sg[n]=0 ; } int main () { memset (sg,-1 ,sizeof (sg)); sg[0 ]=1 ; for (int i=maxn-1 ;i>=1 ;i--) dfs(i); char s[10 ]; int n; while (~scanf ("%s" ,s)) { if (s[0 ]=='0' ) puts ("Yes" ); else { sscanf (s,"%d" ,&n); if (sg[n])puts ("Yes" ); else puts ("No" ); } } }
拓展:翻硬币游戏,Anti-sg游戏,无向图删边游戏
EOF