博弈论入门
知识点:
(1)下面这个链接是奢华版数学家博弈论模型,虽然有点难懂,但是如果做了相当的博弈题目,还是会领悟不少
http://www.bubuko.com/infodetail-806583.html
(2)接下来是穷人版码农博弈论模型,只有各种结论。
取石子游戏:
1.bash博弈:只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。最后取光者得胜。
很显然k*(m+1)是必败点,其余是必胜点。
和上次多校的时候做到的knight、queen、king、castle那题推导的方式类似。首先澄清,必胜点是指先手没有动,但是他存在一种动的方案,使得对方面对必败点。也就是说,如果所有博弈局面组织成一张图,那么必败点的邻接点全部都是必胜点,而必胜点存在一个邻接点是必败点,而行动就是边(败全胜,胜一败,行动边)。那么这个有什么用呢?bash中显然1~m是必胜点,那么m+1是必败点,那么m+2,m+3....2m+1是必胜点,2m+2是必败点,以此类推。简而言之,就是从初始局面倒推(胜败点交替填写)。从某种意义上说,博弈和动规天然一致。
2.威佐夫博弈:有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
sg函数:http://www.cnblogs.com/frog112111/p/3199780.html
奇异局势,所有堆的xor和==0.
假定S是非奇异局势,T是奇异局势。
一堆中石子数量>=2,表示充裕堆, =1表示孤单堆。
S0即非奇异局势下,充裕堆为0的状态
S1即非奇异局势下,充裕堆为1的状态
S2即非奇异局势下,充裕堆>=2的状态
T0即奇异局势下,充裕堆为0的状态
T2即奇异局势下,充裕堆>=2的状态
1.奇异局势的定义可知,S能转移到T,能转移到S, T只能转移到S
2.S0必败,T0必胜
3.S1必胜,因为S1只需要转移到S0即可。
4.S2必胜,T2必败。
1)T2只能转移到S1 和 S2
2)若T2转移到S1 则T2败,若T2转移到S2,S2只需要转回到T2即可。所以S2胜,T2败。
所以:
必胜态:T0,S1,S2
必败态:S0,T2
(3)题目:
hdu1404
http://acm.hdu.edu.cn/showproblem.php?pid=1404
一开局就遇到难题。。。。
给出一个数列每个数字不大于9,轮流要么选一个0删除它以及它右边所有,要么减少一个数字最多到0,删除完数列者获胜。
局面分析感觉比较复杂无规律,所以用sg函数,直接把数列整体看作sg函数的编号。
第二个难点在于sg函数求解,暴力打表一定是超时的,所以换方法,从sg[0]=1往大的sg函数拓展。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<functional> #include<iostream> #include<cmath> #include<string> #include<cctype> #include<stack> #include<set> #include<map> #include<ctime> using namespace std; #define For(i,k,n) for(int i=k;i<=n;i++) #define ForD(i,k,n) for(int i=n;i>=k;i--) #define Lson (x<<1) #define Rson ((x<<1)+1) #define MEM(a) memset(a,0,sizeof(a)); #define NEG(a) memset(a,-1,sizeof(a)); #define INF 0x3f3f3f3f #define LLINF 0x3f3f3f3f3f3f3f3f #define maxn 999999LL #define ll long long int sg[maxn+100],h[maxn+100]; int get_length(int n)//得到整数n的位数 { if(n/100000) return 6; if(n/10000) return 5; if(n/1000) return 4; if(n/100) return 3; if(n/10) return 2; return 1; } int getnum(string str) { int ret=0,sz=str.size(); For(i,0,sz-1) { ret*=10; ret+=str[i]-'0'; } return ret; } void getSG() { MEM(sg); sg[0]=1; For(n,1,maxn) { if(sg[n]) continue; int len=get_length(n); int i; for(i=len;i>=1;i--)//每一个位上加上一个数 { int m=n; int base=1; for(int j=1;j<i;j++) base*=10; int tmp=(m%(base*10))/base; for(int j=tmp;j<9;j++) { m+=base; sg[m]=1;//m为前者必胜点 } } if(len!=6)//长度小于6,则可以在后面加0开头的 { int m=n; int base=1; for(int i=len;i<6;i++) { m*=10; for(int b=0;b<base;b++) sg[m+b]=1; base*=10; } } } } int main() { getSG(); char buf[2000]; while(scanf("%s",buf)==1) { int n=getnum(string(buf)); //printf("%d\n",n ); if(buf[0]=='0') puts("Yes"); else printf(sg[n]==0?"No\n":"Yes\n"); } return 0; }
hdu1907
http://acm.hdu.edu.cn/showproblem.php?pid=1907
一开始没有看出是nim。。。
给出一个堆颜色,每一个颜色数量若干,每次只能从同色取若干个,取完者败。
显然颜色就是堆,然后就是反nim。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<iostream> using namespace std; int main() { int n,t; scanf("%d",&t); while(t--){ scanf("%d",&n); int cnt1=0,cnt2=0,sum=0; for(int i=0;i<n;i++) { int num; scanf("%d",&num); sum^=num; if(num>=2) cnt1++; if(num==1) cnt2++; } if((sum==0&&cnt1==0)||(sum!=0&&cnt1>=1)) printf("John\n"); else printf("Brother\n"); } return 0; }
hdu1517
http://acm.hdu.edu.cn/showproblem.php?pid=1517
从1开始每次轮流选择乘以1~9,令数字>=n的获胜
就是必胜必败点交替互推的方法
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<iostream> using namespace std; #define ll long long int main() { ll n; while(cin>>n) { int flg=0; while(n>1) { if(flg==0){ if(n%9==0) n/=9; else n=n/9+1; flg=1; } else { if(n%2==0) n/=2; else n=n/2+1; flg=0; } } if(flg) puts("Stan wins."); else puts("Ollie wins."); } return 0; }
hdu1849
http://acm.hdu.edu.cn/showproblem.php?pid=1849
一维棋盘,若干棋子,轮流棋子向左移动,无其他限制,一个格子可以多个棋子,无动作者败。
这种不容易编码的显然不能必败必胜交替这样做
每一个棋子都是一个堆,转化为nim
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<iostream> using namespace std; #define ll long long int main() { ll n; while(cin>>n&&n) { int sum=0; for(int i=0;i<n;i++) { int num; scanf("%d",&num); sum^=num; } if(sum==0) { printf("Grass Win!\n"); } else printf("Rabbit Win!\n"); } return 0; }
hdu2188
http://acm.hdu.edu.cn/showproblem.php?pid=2188
两人轮流捐款每次捐款不超过m元,先使得总额超过n的获胜
就算不知道bash博弈也可以推导出k*(m+1)是必败态
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<stdio.h> int main() { int n,m; int T; scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); if(n%(m+1)==0) printf("Rabbit\n"); else printf("Grass\n"); } return 0; }
hdu3970
http://acm.hdu.edu.cn/showproblem.php?pid=3980
一个石圈,每次选取一段长m的连续的石头染色,最后没有行动的败
和bash博弈的区别在于,石圈每次染色后都会产生两个子问题,于是就是sg函数了
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* *********************************************** Author :kuangbin Created Time :2013-11-17 19:20:19 File Name :E:\2013ACM\比赛练习\2013-11-17\H.cpp ************************************************ */ #include <stdio.h> #include <string.h> #include <iostream> #include <algorithm> #include <vector> #include <queue> #include <set> #include <map> #include <string> #include <math.h> #include <stdlib.h> #include <time.h> using namespace std; const int MAXN = 1010; int sg[MAXN]; bool vis[MAXN]; int m; int mex(int n) { if(sg[n] != -1)return sg[n]; if(n < m)return sg[n] = 0; memset(vis,false,sizeof(vis)); for(int i = m;i <= n;i++) vis[mex(i-m)^mex(n-i)] = true; for(int i = 0;;i++) if(vis[i] == false) { sg[n] = i; break; } return sg[n]; } int main() { int T; int n; scanf("%d",&T); int iCase = 0; while(T--) { scanf("%d%d",&n,&m); iCase++; if(n < m) { printf("Case #%d: abcdxyzk\n",iCase); continue; } n -= m; memset(sg,-1,sizeof(sg)); for(int i = 0;i <= n;i++) sg[i] = mex(i); if(sg[n] == 0)printf("Case #%d: aekdycoin\n",iCase); else printf("Case #%d: abcdxyzk\n",iCase); } return 0; }
hdu4478
http://acm.hdu.edu.cn/showproblem.php?pid=4778
感动,写了无数遍终于写对了。。。。。
有n种颜色,m个背包,每个背包指定的各种颜色石头数目,一轮取一个背包,取完之后总是把所有相同颜色的S个石头合成,如果一轮能至少合成一次,则下一轮仍是你的先手。收益为合成次数,使得收益差最大。
显然是dp,dp[j],表示已经用掉背包集合为j时先手能取得的最大收益差,这里坑了很久的地方是坚决不要dp的时候对于轮数进行一重循环,因为集合已经成拓扑序了,如果这里写一重循环会无限tle扑街。转移是如果不能合成,就收益取相反数,否则收益加上当前转移合成的次数。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<cstring> #include<vector> #include<cstdlib> #include<algorithm> #include<functional> #include<iostream> #include<cmath> #include<string> #include<cctype> #include<stack> #include<set> #include<map> #include<ctime> using namespace std; #define For(i,k,n) for(int i=k;i<=n;i++) #define ForD(i,k,n) for(int i=n;i>=k;i--) #define Lson (x<<1) #define Rson ((x<<1)+1) #define MEM(a) memset(a,0,sizeof(a)); #define NEG(a) memset(a,-1,sizeof(a)); #define INF 0x3f3f3f3f #define LLINF 0x3f3f3f3f3f3f3f3f #define maxn ((1<<21)+100) #define ll long long #define print(b,a) cout<<b<<"="<<a<<endl; #define printbin(b,a){int tmp=a;string s;do{s+=tmp%2+'0';tmp/=2;}while(tmp);reverse(s.begin(),s.end());cout<<"bin "<<b<<"="<<s<<endl;} #define printarr(i,a,f,b) {For(i,f,b) printf("%d ",a[i]); printf("\n");} int d[maxn]; int a[maxn][10],G,S,B; int bag[22][20]; void init(){ For(i,0,(1<<B)-1) { For(j,0,B-1) { if(((i>>j)&1)==0) continue; For(k,1,G) a[i][k]+=bag[j][k]; } For(j,1,G) a[i][j]%=S; /*print("i",i); printarr(q,a[i],1,G);*/ } } int solve() { d[(1<<B)-1]=0; ForD(j,0,(1<<B)-1) { For(k,0,B-1) { if(((j>>k)&1)==0) { int s=j|(1<<k); if(d[s]==-INF) continue; int tmp=d[s]; int cnt=0; int b[10]; For(q,1,G) b[q]=a[j][q]+bag[k][q]; For(q,1,G) cnt+=b[q]/S; if(cnt==0) d[j]=max(d[j],-tmp); else d[j]=max(d[j],tmp+cnt); } } } return d[0]; } int main() { //freopen("in.txt","r",stdin); while(scanf("%d%d%d",&G,&B,&S)&&!(G==0&&B==0&&S==0)) { For(j,0,maxn-5) d[j]=-INF; MEM(a); MEM(bag); For(i,0,B-1) { int n; scanf("%d",&n); For(j,0,n-1) { int num;scanf("%d",&num); bag[i][num]++; } } init(); printf("%d\n",solve()); } }
hdu4764
http://acm.hdu.edu.cn/showproblem.php?pid=4764
轮流写数字,一开始数字1~k,每一轮数字要求和之前的差>=1&&<=k,写下不小于n的人输
显然本质就是巴佐夫博弈,值得注意的只是最开始的先手局面是0不是1
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> #include <string.h> #include <iostream> #include <algorithm> #include <vector> #include <queue> #include <set> #include <map> #include <string> #include <math.h> #include <stdlib.h> #include <time.h> using namespace std; int n,k; int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); while(scanf("%d%d",&n,&k) == 2) { if(n == 0 && k == 0)break; if((n-1)%(k+1) == 0)printf("Jiang\n"); else printf("Tang\n"); } return 0; }
hdu1847
http://acm.hdu.edu.cn/showproblem.php?pid=1847
给定n张牌,每次只能取add2的幂张,先取完的胜利
开一个win数组纪录是否是必胜态,然后逆序交替填写必胜必败就可以了
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<cstring> #include<cstdlib> #include<vector> #include<algorithm> #include<functional> #include<iostream> #include<cmath> #include<string> #include<cctype> #include<stack> #include<set> #include<map> #include<ctime> using namespace std; #define For(i,k,n) for(int i=k;i<=n;i++) #define ForD(i,k,n) for(int i=n;i>=k;i--) #define Lson (x<<1) #define Rson ((x<<1)+1) #define MEM(a) memset(a,0,sizeof(a)); #define NEG(a) memset(a,-1,sizeof(a)); #define INF 0x3f3f3f3f #define LLINF 0x3f3f3f3f3f3f3f3f #define maxn ((1<<21)+100) #define ll long long #define print(b,a) cout<<b<<"="<<a<<endl; #define printbin(b,a){int tmp=a;string s;do{s+=tmp%2+'0';tmp/=2;}while(tmp);reverse(s.begin(),s.end());cout<<"bin "<<b<<"="<<s<<endl;} #define printarr(i,a,f,b) {For(i,f,b) printf("%d ",a[i]); printf("\n");} int a[10]={1,2,4,8,16,32,64,128,256,512}; int n,win[2000],vis[2000]; int solve(int n){ MEM(vis); win[n]=0; ForD(i,0,n-1) { int loseflg=0; For(k,0,9) { if(i+a[k]<=n&&win[i+a[k]]==0) loseflg=1; } win[i]=loseflg; } return win[0]; } int main() { int n; while(scanf("%d",&n)==1) { if(solve(n)) { puts("Kiki"); } else puts("Cici"); } return 0; }
hdu2147
http://acm.hdu.edu.cn/showproblem.php?pid=2147
从棋盘右上角开始轮流往左,下或者左下走,无法行动者败
这题不难,但是如果p、n态轮流填写的话内存有限制,所以打个表找规律
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<cstring> #include<cstdlib> #include<vector> #include<algorithm> #include<functional> #include<iostream> #include<cmath> #include<string> #include<cctype> #include<stack> #include<set> #include<map> #include<ctime> using namespace std; #define For(i,k,n) for(int i=k;i<=n;i++) #define ForD(i,k,n) for(int i=n;i>=k;i--) #define Lson (x<<1) #define Rson ((x<<1)+1) #define MEM(a) memset(a,0,sizeof(a)); #define NEG(a) memset(a,-1,sizeof(a)); #define INF 0x3f3f3f3f #define LLINF 0x3f3f3f3f3f3f3f3f #define maxn ((1<<21)+100) #define ll long long #define print(b,a) cout<<b<<"="<<a<<endl; #define printbin(b,a){int tmp=a;string s;do{s+=tmp%2+'0';tmp/=2;}while(tmp);reverse(s.begin(),s.end());cout<<"bin "<<b<<"="<<s<<endl;} #define printarr(i,a,f,b) {For(i,f,b) printf("%d ",a[i]); printf("\n");} //int t[2001][2001]; int d[3][2]={{1,-1},{0,-1},{1,0}}; int n,m; /*int dfs(int i,int j) { if(t[i][j]!=-1) return t[i][j]; t[i][j]=0; For(k,0,2) { int newi=i+d[k][0]; int newj=j+d[k][1]; if(newi>=1&&newi<=n&&newj>=1&&newj<=m) { t[i][j]|=dfs(newi,newj)^1; } } return t[i][j]; }*/ void solve() { if(n%2==0||(n%2==1&&m%2==0)) puts("Wonderful!"); else puts("What a pity!"); } int main() { while(scanf("%d%d",&n,&m)==2&&!(n==0&&m==0)) { //NEG(t); solve(); } }
hdu1850
http://acm.hdu.edu.cn/showproblem.php?pid=1850
尼姆博弈,问有多少种方案必胜
分析的时候犯傻,尼姆博弈每次只能在一堆取!所以只需考虑每一堆数目是不是够使得总异或为0
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<cstring> #include<cstdlib> #include<vector> #include<algorithm> #include<functional> #include<iostream> #include<cmath> #include<string> #include<cctype> #include<stack> #include<set> #include<map> #include<ctime> using namespace std; #define For(i,k,n) for(int i=k;i<=n;i++) #define ForD(i,k,n) for(int i=n;i>=k;i--) #define Lson (x<<1) #define Rson ((x<<1)+1) #define MEM(a) memset(a,0,sizeof(a)); #define NEG(a) memset(a,-1,sizeof(a)); #define INF 0x3f3f3f3f #define LLINF 0x3f3f3f3f3f3f3f3f #define maxn 200 #define ll long long #define print(b,a) cout<<b<<"="<<a<<endl; #define printbin(b,a){int tmp=a;string s;do{s+=tmp%2+'0';tmp/=2;}while(tmp);reverse(s.begin(),s.end());cout<<"bin "<<b<<"="<<s<<endl;} #define printarr(i,a,f,b) {For(i,f,b) printf("%d ",a[i]); printf("\n");} int a[maxn]; int main(){ int m; while(scanf("%d",&m)==1&&m) { int sum=0,ans=0; For(i,1,m){int num;scanf("%d",&a[i]);sum^=a[i];} if(sum==0) puts("0"); else { For(i,1,m) if(a[i]>(sum^a[i])) ans++; printf("%d\n",ans ); } } return 0; }
hdu1851
http://acm.hdu.edu.cn/showproblem.php?pid=1851
n堆的bash博弈
每堆的胜负的总异或
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<stdio.h> int main() { int T; int i,n; int ans,m,l; scanf("%d",&T); while(T--) { scanf("%d",&n); ans=0; for(i=1;i<=n;i++) { scanf("%d%d",&m,&l); ans=ans^(m%(l+1)); } if(ans==0) printf("Yes\n"); else printf("No\n"); } return 0; }
hdu2897
http://acm.hdu.edu.cn/showproblem.php?pid=2897
有上下界限的一堆bash博弈
结论很显然,n%(p+q)>=1&&<=p是必败点,值得注意的就两点,1是坑爹的最后换行忘记了居然测试的时候看不出来而且报错wa,2是建议从0开始而不是从n开始做n、p讨论
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<cstring> #include<cstdlib> #include<vector> #include<algorithm> #include<functional> #include<iostream> #include<cmath> #include<string> #include<cctype> #include<stack> #include<set> #include<map> #include<ctime> using namespace std; #define For(i,k,n) for(int i=k;i<=n;i++) #define ForD(i,k,n) for(int i=n;i>=k;i--) #define Lson (x<<1) #define Rson ((x<<1)+1) #define MEM(a) memset(a,0,sizeof(a)); #define NEG(a) memset(a,-1,sizeof(a)); #define INF 0x3f3f3f3f #define LLINF 0x3f3f3f3f3f3f3f3f #define maxn 200 #define ll long long #define print(b,a) cout<<b<<"="<<a<<endl; #define printbin(b,a){int tmp=a;string s;do{s+=tmp%2+'0';tmp/=2;}while(tmp);reverse(s.begin(),s.end());cout<<"bin "<<b<<"="<<s<<endl;} #define printarr(i,a,f,b) {For(i,f,b) printf("%d ",a[i]); printf("\n");} int main(){ int n,p,q; while(scanf("%d%d%d",&n,&p,&q)==3) { int res=n%(p+q); if(res>=1&&res<=p) printf("LOST\n"); else printf("WIN\n"); } return 0; }
hdu