博弈论,今天算是告一段落了。
①博弈模型为两个人轮流决定的非合作博弈,即两个人轮流进行决策,并且每次都会采用最优策略。
②博弈模型必须是有限布可以完成的。
③对两个人的规则是公平的。
P状态(必败态):前一个选手(Previous player)将取胜的位置称为必败点。
N状态(必胜态):下一个选手(Next player)将取胜的位置称为必胜点。
①将所有终止状态定义为P状态
②将所有可以一步到达P状态的点,定义为N状态
③如果存在某个状态,它的下一步都是N状态,定义为P状态
④循环②,③步骤
一)巴什博奕(Bash Game):只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。最后取光者得胜。
判断条件:n%(m+1),不为零时获胜。第一步取走n%(m+1)个,然后根据第二个人所取数目k,跟着他取走(m+1-k)个即可。
二)威佐夫博奕(Wythoff Game):有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜
判断条件:使用(ak,bk)来表示两个堆里面的物品数,则P状态满足:ak=k*[(1+√5)/2],bk=ak+k,(k=0,1,2,3......);
三)尼姆博奕(Nimm Game)简称Nim模型:有三堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
判断条件:使用(a1,a2,a3)来描述所处状态,ans=a1^a2^a3,如果ans为零则为P状态。
定理1:Nim游戏的一个状态(x1, x2, x3) 是P状态,当且仅当x1+x2+x3=0。多与三个状态一样满足该定理
定理的证明自己网上找。
自己的理解:Nim模型,为多种情况相同处理的并,而且处理只能是取走任意颗石子
四)Sprague-Grundy函数(简称SG函数):有n堆石子,每次可以从第1堆石子里取1颗、2颗或3颗,可以从第2堆石子里取奇数颗,可以从第3堆及以后石子里取任意颗,最后取光着得胜。
首先来了解一下什么为SG函数,对于一个递增有界的图G(X,F)来说,SG函数g,是定义在X上的函数,函数值是非负整数,使得g(x)的值等于所有x的后继的SG函数中没有出现的最小非负整数。对于递增有界的图,SG函数是唯一的、有界的。所有的终止状态x,因为F(x)是空集,所以g(x)=0.
根据定义,考虑以下三点:
如果x是终止状态,那么g(x)=0。
一个状态x,如果g(x)≠0,那么一定存在一个x的后继y,使得g(y)=0。
一个状态x,如果g(x)=0,那么所有x的后继y,都有g(y)≠0。
这三句话表明,顶点x所代表的position是P-position当且仅当g(x)==0.
计算出所有情况的SG值,最后求异或g(G)=g(G1)^g(G2)^g(G3)........,当g(G)==0时为P状态。
定理2:
设G=G1+G2+…+Gn,Gi的SG函数是gi,i=1, 2, …, n。那么G的SG函数g(x1, x2, …, xn)=g1(x1)+g2(x2)+…+gn(xn),加法表示Nim和,即不进位的二进制加法。
自己的理解:SG模型,为多种情况不同处理的并,每次都需要求一次g函数,处理可以随便进行
5)补充:阶梯博弈:n个阶梯,阶梯上有一些石子,要求将阶梯上的石子都放到地上,操作只能是从第i个阶梯取若干个石子到第i-1上
判断条件:对只需要对奇数号码的阶梯进行Nim运算即可。
先取者使状态到P状态,如果后面一个人也从奇数号码阶梯取石子,则按照Nim步骤操作即可,从奇数阶梯到偶数阶梯相当于取出石子扔掉。若从偶数号码阶梯取石子,则先取者只需要将相同的石子从个该阶梯传到下面一个即可。
这里有详细介绍:http://blog.csdn.net/kk303/article/details/6692506
http://www.cnblogs.com/Knuth/archive/2009/09/05/1561005.html,寻找必败态——一类博弈问题的快速解法
这其实也是每类博弈题最先考虑的问题,或者突破点所在。
http://www.cnblogs.com/Knuth/archive/2009/09/05/1561002.html,Game theory初步,对两个定理有详细的证明
http://www.cnblogs.com/Knuth/archive/2009/09/05/1561008.html,Nim游戏
http://www.cnblogs.com/Knuth/archive/2009/09/05/1561007.html,Sprague-Grundy函数
http://www.cnblogs.com/tanky_woo/archive/2010/08/20/1804464.html,组合博弈知识汇总,这个博客里面有很多的补充
hdoj1846,题型①
//AC,最简单的博弈题 #include <cstdio> int main() { //freopen("f://data.in","r",stdin); int C,n,m; scanf("%d",&C); while(C--) { scanf("%d %d",&n,&m); if(n%(m+1)) printf("first\n"); else printf("second\n"); } return 0; }
//取石子游戏---威佐夫博奕(Wythoff Game) #include <cstdio> #include <cmath> int main() { double t=(1+sqrt(5.0))/2.0; int a,b; while(scanf("%d%d",&a,&b)==2) { if(a>b) { int temp=a; a=b; b=temp; } int k=b-a; if(a==(int)(k*t)) printf("0\n"); else printf("1\n"); } return 0; }
hdoj1850,Nim模型
//Nim组合博弈问题 #include <cstdio> const int nMax=1000000+10; int M,N[nMax]; int main() { //freopen("f://data.in","r",stdin); while(scanf("%d",&M) && M) { int count=0,ans=0; for(int i=0;i<M;i++) { scanf("%d",&N[i]); ans^=N[i]; } /*这里有个技巧,首先求出所有的异或值,如果想去除的话,只需要再次进行异或操作即可,因为x^x=0,执行两次等于没有执行。原来每次都需要进行一次计算,这样可使算法效率从O(n*n)缩小到了O(n)*/ for(int i=0;i<M;i++) { if((ans^N[i])<N[i]) count++; } printf("%d\n",count); } return 0; }
这里涉及到一些不同的东西。直接写结论:必败态中有一种情况需要单独判断,奇数个孤单堆(即奇数个堆石子数为1)为必败态。还有一点是,两人在后面取石子的方式会有所不同,需要注意,详细解释在http://www.cnblogs.com/tanky_woo/archive/2010/08/20/1804464.html里面有。当超过1颗石子的堆数大于1的时候,按照Nim的方法走。直到超过1颗石子的堆数等于1,这时将这堆石子全部取掉或剩1颗,保证非空(剩下1颗石子)的堆数为奇数。
//这个问题关键在最后取走的为失败者。 #include <cstdio> int main() { //freopen("f://data.in","r",stdin); const int nMax=4747+10; int T,N,A[nMax]; scanf("%d",&T); while(T--) { scanf("%d",&N); int ans=0; int sum=0; for(int i=0;i<N;i++) { scanf("%d",&A[i]); ans^=A[i]; sum+=A[i]; } if(sum==N) { printf("%s\n",sum%2?"Brother":"John"); } else { if(ans) printf("John\n"); else printf("Brother\n"); } } return 0; }
//第一种解法,历编自己找到规律 #include <cstdio> int main() { int n; while(scanf("%d",&n)!=EOF) { if(n%3==0) printf("Cici\n"); else printf("Kiki\n"); } return 0; } //第二种解法 #include <cstdio> #include <cstring> const int nMax=1000+10; int a[10]; int g[nMax]; void init() { for(int i=0;i<10;i++) a[i]=1<<i; memset(g,-1,sizeof(g)); g[0]=0; } int dfs(int n) { if(g[n]!=-1) return g[n]; bool h[15];//从①处可得知数组的大小 memset(h,0,sizeof(h)); int i; for(i=0;i<10 && n-a[i]>=0;i++) h[dfs(n-a[i])]=1;//① for(i=0;h[i];i++); return g[n]=i; } int main() { //freopen("f://data.in","r",stdin); int n; init(); while(scanf("%d",&n)==1) { if(dfs(n)) printf("Kiki\n"); else printf("Cici\n"); } return 0; }
#include <cstdio> #include <cstring> const int mMax=105,nMax=10005; int k,m,n; int S[mMax],H[mMax]; int g[nMax]; bool h[nMax][mMax];//从②处可得知,二维数组的大小。 void init() { for(int i=0;i<k;i++) scanf("%d",&S[i]); memset(g,-1,sizeof(g)); memset(h,0,sizeof(h)); g[0]=0; } int dfs(int n) { if(g[n]!=-1) return g[n]; for(int i=0;i<k;i++) if(S[i]<=n)//①因为S并不一定是从小到大排序好的,所以只能用if语句进行判断。 //纠错时,不要去找语法,语法不会有错,要去发现算法,哪一步出现了问题。或许是某个细节没有注意。 h[n][dfs(n-S[i])]=1;//② int i; for(i=0;h[n][i];i++); return g[n]=i; } void solve() { scanf("%d",&m); while(m--) { int ans=0; scanf("%d",&n); for(int i=0;i<n;i++) { scanf("%d",&H[i]); ans^=dfs(H[i]); } if(ans) printf("W"); else printf("L"); } printf("\n"); } int main() { //freopen("f://data.in","r",stdin); for(;;) { scanf("%d",&k); if(!k) break; init(); solve(); } return 0; }
#include <cstdio> #include <cstring> const int nMax=17; int F[nMax],N; int g[1005]; void init() { int i; F[1]=1; F[2]=2; for(i=3;F[i-1]<1000;i++) F[i]=F[i-1]+F[i-2]; N=i-1; memset(g,-1,sizeof(g)); } int f(int n) { if(g[n]!=-1) return g[n]; bool h[nMax]; memset(h,0,sizeof(h)); int i; for(i=1;i<=N && F[i]<=n;i++) h[f(n-F[i])]=1; for(i=0;h[i];i++); return g[n]=i; } int main() { //freopen("f://data.in","r",stdin); int m,n,p; init(); for(;;) { scanf("%d%d%d",&m,&n,&p); if(m==0 && n==0 && p==0) break; int ans=f(m)^f(n)^f(p); printf("%s\n",ans==0?"Nacci":"Fibo"); } return 0; }
/* 思路:阶梯博弈 建模:将两个棋子之间的位置数看做石子堆,棋子向左移动看做从左侧石子堆向右侧石子堆转移。 所以该题可转换成阶梯博弈模型进行求解。 */ #include <cstdio> #include <cstdlib> const int nMax=1010; int T,N,P[nMax]; int cmp(const void *a,const void *b) { int *pa=(int *)a; int *pb=(int *)b; return *pa-*pb; } int main() { //freopen("f://data.in","r",stdin); scanf("%d",&T); while(T--) { scanf("%d",&N); P[0]=0; for(int i=1;i<=N;i++) scanf("%d",&P[i]); qsort(P,N+1,sizeof(P[0]),cmp); int ans=0; for(int i=N;i>=1;i-=2) { ans^=P[i]-P[i-1]-1; } printf("%s\n",ans==0?"Bob will win":"Georgia will win"); } }
/* 思路:寻找失败类 这道题是自己做出来的好兴奋啊,原来WA, 通过测试特殊数据找出了程序细节处理上的错误 */ #include <cstdio> int op(long long n) { if(n%18==0) return n/18; else return n/18+1; } int main() { //freopen("f://data.in","r",stdin); long long n;//使用double类型也可通过 while(scanf("%lld",&n)==1) { /*if(n==1) { printf("Stan wins.\n"); continue; } while(n>18) n=op(n); if(n>1 && n<=9) printf("Stan wins.\n"); else printf("Ollie wins.\n");*/ //或者 while(n>18) n=op(n);//因为这里在n=18时候会停止循环,边界处理没有明确,1<=n<=18; if(n<=9) printf("Stan wins.\n"); else printf("Ollie wins.\n"); } return 0; }