(1)巴什博弈
有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。最后取光者得胜。
这个游戏是有规律的,并且每个人都采取理智最优的话,游戏一开始就决定了胜负。
与之类似的是报数,从1开始报,最少报数加一个,最多报数加3个,谁先报到20谁赢。
第一个人无论是报1,2,3,第二个人每次取4的倍数都是可以的。如此往复一定是第二个人赢。
即存在公式:N=(K+1)*X+M
在本例中K=3,N=20,那么X=5,M=0,如此一来一定是第二个人赢,而如果M!=0,则第一个人报M,第二个人无论报多少,第一个人再重新报M+(K+1),如此第一个人是必胜的。
例子:
http://acm.hdu.edu.cn/showproblem.php?pid=4764
代码:
1 #include<iostream> 2 using namespace std; 3 4 int main() 5 { 6 int N,K; 7 while(scanf("%d%d",&N,&K)) 8 { 9 if(N==0)break; 10 else 11 { 12 if((N-1)%(K+1)==0) 13 printf("Jiang "); 14 else 15 printf("Tang "); 16 } 17 } 18 return 0; 19 }
(2)威佐夫博弈
有两堆石子,数量任意,可以不同。游戏开始由两个人轮流取石子。游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。现在给出初始的两堆石子的数目,如果轮到你先取,假设双方都采取最好的策略,问最后你是胜者还是败者。
简单判断威佐夫博弈胜负:
http://acm.hdu.edu.cn/showproblem.php?pid=1527
需要输出第一步取石子策略:
http://acm.hdu.edu.cn/showproblem.php?pid=2177
关于这个博弈是存在必败格局的,而且必败格局有一定的规律。两堆石子等效,故而假设第一堆数目少与等于第二堆。
第一个必败格局为:(0,0),即如果假设A当前面对此数目石子,说明B已经在上一步骤取完。
第二个必败格局为:(1,2),如果A当前面对此数目石子,A取完为(1,1)或(0,2),B取完之后A面对的格局依旧是(0,0)
第三个必败格局为:(3,5),依次列举为:(4,7)(6,10)。。。
对于威佐夫博弈有如下规律:
我们用a[i]表示失败态中的第一个,b[i]表示失败态中的第二个.(i从0开始).
那么我们可以看到b[i] = a[i]+i;(i >= 0),a[i]是前面的失败态中没有出现过的最小的整数。
1.每个数仅包含在一个失败态中
可以根据递推式得到此性质
2.每个失败态可以转到非失败态
3.每个非失败态都可以转到一个失败态
k=b-a;if (a-ak)=(b-bk); (a-ak)>0,(b-bk)>0
则转化为(a-(a-ak),b-(b-bk));
在ak中可以找到与a相等的,或bk中可以找到与a相等的,则转化为(ak,bk);
这个性质决定了,如果当前不是失败态,我们如何让对手输掉这场博弈。
4.可以用下面的方法来判断失败态
a[i] = [i*(1+√5)/2](这里的中括号表示向下取整),b[i]=a[i]+i;
那么这就是一个失败态。
利用性质4解决第一个问题:
#include<iostream> #include<cmath> using namespace std; void change(int &a,int &b) { int t=a; a=b; b=t; } int main() { int a,b; while(scanf("%d%d",&a,&b)!=EOF) { if(a>b) change(a,b); double x=(sqrt(5.0)+1)/2; if((int)((b-a)*x)==a) printf("0 "); else printf("1 "); } return 0; }
利用性质3和性质4解决第二个问题:
首先打必败局表,然后写一个二分查找用来定位,再然后就是利用性质3的两条,分别将所有可能的先手步骤打印出来。
思路用的是讨论区最初的那个思路
http://acm.hdu.edu.cn/discuss/problem/post/reply.php?postid=12441&messageid=1&deep=0
虽然可以AC,但是许多人也提到了这个题目的数据比较水,容易通过。
#include<iostream> #include<cmath> using namespace std; #define MAX 1000001 int a[MAX],b[MAX]; int record[MAX+MAX]={0}; int num; void calc() { int cnt=0; record[0]=1; int i; for(i=1;a[i-1]<=MAX;i++) { while(record[cnt]!=0) cnt++; a[i]=cnt; b[i]=cnt+i; record[a[i]]=1;record[b[i]]=1; } num=i; } int findA(int num,int l,int r) { int mid=(l+r)/2; if(a[mid]==num) return mid; else { if(l>=r)return 0; if(a[mid]>num) findA(num,l,mid-1); else findA(num,mid+1,r); } } int findB(int num,int l,int r) { int mid=(l+r)/2; if(b[mid]==num) return mid; else { if(l>=r)return 0; if(b[mid]>num) findB(num,l,mid-1); else findB(num,mid+1,r); } } int main() { calc(); int one,two; while(scanf("%d%d",&one,&two)) { if(one==0&&two==0)break; double x=(sqrt(5.0)+1)/2; if((int)((two-one)*x)==one) printf("0 "); else { printf("1 "); int k=two-one; if((one-a[k]==two-b[k])&&(one-a[k])>0&&(two-b[k])>0) printf("%d %d ",one-(one-a[k]),two-(two-b[k])); int onepro=findA(one,0,num-1); if(onepro!=0) printf("%d %d ",one,b[onepro]); else { int oneB=findB(one,0,num-1); printf("%d %d ",a[oneB],one); } } } return 0; }
输入:5 8
输出:
1
4 7
3 5