博弈小题狂做1
最近看视频看得心都要苏了~导致做题差点做哭,呜呜~(ㄒoㄒ)
hdu2897 邂逅明下(巴什博奕)
题意:每行有三个数字n,p,q,表示一堆硬币一共有n枚,从这个硬币堆里取硬币,一次最少取p枚,最多q枚,如果剩下少于p枚就要一次取完。两人轮流 取,直到堆里的硬币取完,最后一次取硬币的算输。对于每一行的三个数字,给出先取的人是否有必胜策略,如果有回答WIN,否则回答LOST。
题解:必败的状态是面临(p+q)*k+r,(r<=p),因为你若取x个,对方一定会取p+q-x个,最后剩下r个给你,你必败。
吐槽:MM说:好小子,挺聪明呢,要不这样吧,你把我的邮箱给我~
1 #include<cstdio> 2 int main(){ 3 int n,p,q; 4 while(scanf("%d%d%d",&n,&p,&q)==3){ 5 n=n%(p+q); 6 if(n>0&&n<=p)printf("LOST\n"); 7 else printf("WIN\n"); 8 } 9 return 0; 10 }
题意:在一个m*n的棋盘内,从(1,m)点出发,每次可以进行的移动是:左移一,下移一,左下移一。先到左下角则胜。kiki先走问是否会赢
1 #include<cstdio> 2 int main(){ 3 int a,b; 4 while(scanf("%d%d",&a,&b),a||b){ 5 if((a&1)&&(b&1))printf("What a pity!\n"); 6 else printf("Wonderful!\n"); 7 } 8 return 0; 9 }
题意:有两堆石子,两人依次取石子,每次从较多的那堆取数目为较少那堆的整数倍,取完一堆者胜,问先手胜负情况。
题解:若两堆石子数相同或其中一堆为0,那么当前取的人胜。
假设状态1为(x,y),(x>y),状态2为(x+y,y),能到达状态1,则两者之间一个为必胜一个为必败状态。状态3(z+2y,y)既可以到达状态1又可以到达状态2,所以为必胜状态,同理若满足x>=2*y,(x>y)则为必胜状态。
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 int main(){ 5 int x,y,f; 6 while(scanf("%d%d",&x,&y),x||y){ 7 f=1; 8 while(1){ 9 if(x<y)swap(x,y); 10 if(x-y>=y)break; 11 if(x==0||x==y)break; 12 f^=1; 13 x-=y; 14 } 15 if(f)printf("Stan wins\n"); 16 else printf("Ollie wins\n"); 17 } 18 return 0; 19 }
题意:从数字p=1开始,Stan先手,两人轮流乘以一个2~9的数,先乘到p>=n则胜
题解:找规律:
[2,9]:Stan必胜
[9+1,9*2]:Ollie
[9*2+1,9*2*9]:Stan
[9*2*9+1,9*2*9*2]:Ollie
1 #include<cstdio> 2 #include<cmath> 3 using namespace std; 4 int main(){ 5 int n; 6 while(scanf("%d",&n)==1){ 7 while(n>18) 8 n=ceil(n*1./18); 9 if(n<=9) printf("Stan wins.\n"); 10 else printf("Ollie wins.\n"); 11 } 12 return 0; 13 }
题意:在一行有N个方格的棋盘里,左右分别放了K个白色和黑色的棋子。Alice使左面的白色棋子向右走,Bob使右面的黑色棋子向左走。每次移动,要让自己的一个棋子前进到对应方向的前方的空格子。不能继续决策的人赢。
官方题解:
k=1时很容易看出,n为奇数则后手获胜,n为偶数则先手获胜。
k>1时如果n=2*k+1,则棋 盘中只有一个空白的格子,每次移动必须移动到当前的空白格子上。先手方可以先随意选择一颗棋子占据中间的位置,然后双方互有移动,移动过程中双方肯定都会 选择一颗在己方半场的棋子移动到对方半场里。直到后手方还剩下一颗己方半场的棋子时,先手方把占据中间的棋子移动到对方半场,此时后手方必须移动剩下的这 颗棋子到空出的中间的格子里,先手方再把最后一颗棋子移动到新空出的位置即可获胜。
如果n>2*k+1,那么棋盘中就有了多余一个的 空白格子。如果n为奇数,先手方只要每次移动己方最靠后的一颗棋子即可获胜。并且第一次移动必须选择1号棋子,否则会让自己的棋子中间留下空白相当于把先 手让给了对方,这时对方只要采用先手策略即可获胜;如果n为偶数,那么先手方只需采用n=2*k+1时的策略在双方交会的时候保住先手即可,当然此时的第 一步还是1号棋子,否则将失去先手优势。
1 #include<cstdio> 2 int main(){ 3 int n,k,i=1; 4 while(scanf("%d%d",&n,&k)==2){ 5 printf("Case %d: ",i++); 6 if(k==1)printf(n&1?"Bob\n":"Alice 1\n"); 7 else if(n==2*k+1)printf("Alice %d\n",k); 8 else printf("Alice 1\n"); 9 } 10 return 0; 11 }
题意:n个硬币围成一个圈,Alice与Bob轮流从圈中取硬币,每次可以取一枚或者连续的两枚,硬币取走后留下的空位不用填补,空位相隔的两个硬币视为不相邻,Alice第一个开始取,取走最后一个硬币的人为胜利者。
题解:n<=2时先手Alice必胜,n>=3时,无论Alice怎么取,Bob都可以模仿对手动作来构造出中心对称的图形,所以后手Bob能最后取光,必胜。
1 #include<cstdio> 2 int main(){ 3 int n; 4 while(scanf("%d",&n),n){ 5 printf("%s\n",n>2?"Bob":"Alice"); 6 } 7 return 0; 8 }
题意:有n堆石子,两人轮流操作,操作有两步:第一步从每堆取走至少一个,第二步(可省略)把剩余石子的一部分分给其他某些堆。最后谁无子可取则输。
题解:首先考虑两堆相等的情况,一定先手必败,因为后手可以一直做对称的操作。进而得出对于任意成对相等的情况,都是先手必败。
其他情况都是先手必胜。因为任何情况都可以转化为成对相等情况。若总堆数为奇数,则把最多的一堆分给其他堆,使得其他堆成对相等。(注意,最多一堆的石子是足够多来补齐的,证明:可以把石子从小到大排序后画成条形统计图,相邻两个一组,将需要填补的差距投影到左侧y轴,发现这些不连续区间长度之和,是小于第n堆的)堆数为偶数时将最多的一堆减少到和最少一堆一样多,将其他石子填补其他堆即可。
吐槽:题目没给输赢条件O__O "…
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 int main(){ 5 int n,i,a[10],f; 6 while(scanf("%d",&n),n){ 7 for(i=0;i<n;++i) scanf("%d",&a[i]); 8 if(n&1) printf("1\n"); 9 else{ 10 f=0; 11 sort(a,a+n); 12 for(i=0;i<n;i+=2){ 13 if(a[i]!=a[i+1]){ 14 f=1;break; 15 } 16 } 17 printf("%d\n",f); 18 } 19 } 20 return 0; 21 }
poj2975 Nim(尼姆博弈)
hdu1850 Being a Good Boy in Spring Festival(两题一样的哦~)
题意:有n堆石子,若你处于必败点则输出0,否则输出有多少方法能赢。(第一步有几种取法)
题解:令ans=a1^a2…^an,现在要构造出异或值为0的数。对于某堆石子ai,ans^ai为除了ai以外其他石子的异或值,若ans^ai<=ai,则可以将ai减小到ans^ai,使得所有数的异或值为0
1 #include<cstdio> 2 const int N=1001; 3 int n,a[N]; 4 int main(){ 5 int i,s,ans; 6 while(scanf("%d",&n),n){ 7 for(s=i=0;i<n;++i){ 8 scanf("%d",&a[i]); 9 s^=a[i]; 10 } 11 for(ans=i=0;i<n;++i) 12 if((s^a[i])<a[i]) 13 ans++; 14 printf("%d\n",ans); 15 } 16 return 0; 17 }
poj1704 Georgia and Bob(阶梯博弈)
题意:有1*M的棋盘,给出N枚棋子所在位置,每次选一个棋子只能向左移动,且不能跨过前面的棋子,最左边的棋子只能移动到1位置。问先手是否能赢。
题解:将棋子位置升序排序,从后往前两两绑成一对,若总数为奇数,则把最前面的棋子和边界绑定。在同一对棋子中,若对手移动前一个,你总能将后一个移动相同步数,所以一对棋子的前一个与前一对棋子的后一个之间的距离对最终结果无影响。只需考虑同一对棋子之间的空位,这样就成了N堆Nim游戏。
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 int main(){ 5 int t,n,i,a[1001],ans; 6 scanf("%d",&t); 7 while(t--){ 8 scanf("%d",&n); 9 for(i=0;i<n;++i)scanf("%d",&a[i]); 10 sort(a,a+n); 11 i=1;ans=0; 12 if(n&1){i=2;ans^=(a[0]-1);} 13 while(i<n){ 14 ans^=(a[i]-a[i-1]-1); 15 i+=2; 16 } 17 if(ans)printf("Georgia will win\n"); 18 else printf("Bob will win\n"); 19 } 20 return 0; 21 }
hdu4315 Climbing the Hill(阶梯博弈)
题意:有N人爬山,山顶坐标为0,人的坐标按升序给出,不同坐标只能容纳一人(山顶不限),两人轮流让一人移动任意步数(不能超过前面的人)。现在有一人是King,谁将King移动到山顶则赢。
题解:和上题类似,除了两种特殊情况:
①King是第一个人,先手胜;
②King是第二个人,且一共有奇数个人时,第一堆大小要减一。
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 int main(){ 5 int t,n,k,i,a[1001],ans; 6 while(scanf("%d%d",&n,&k)==2){ 7 a[0]=0; 8 for(i=1;i<=n;++i)scanf("%d",&a[i]); 9 if(k==1){printf("Alice\n");continue;} 10 i=2;ans=0; 11 if(n&1){ 12 i=3; 13 ans=a[1]; 14 if(k==2)ans--; 15 } 16 while(i<=n){ 17 ans^=(a[i]-a[i-1]-1); 18 i+=2; 19 } 20 if(ans)printf("Alice\n"); 21 else printf("Bob\n"); 22 } 23 return 0; 24 }
poj2068 Nim(尼姆博弈,DP)
题意:有两个队,每队n人,每人每次有数量限制,取随后一块的输,问获胜策略。
题解:dp[i][j]表示第i个人取,还剩j块石头。当j=0时为胜。后继中有必败态的为必胜态。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 int dp[20][8200],a[20],n; 6 int dfs(int id,int s){ 7 if(dp[id][s]+1) return dp[id][s]; 8 for(int i=max(0,s-a[id]);i<s;++i) 9 if(!dfs((id+1)%n,i)) return dp[id][s]=1; 10 return dp[id][s]=0; 11 } 12 int main(){ 13 int i,s,ans; 14 while(scanf("%d",&n),n){ 15 scanf("%d",&s); 16 n<<=1; 17 for(i=0;i<n;++i) scanf("%d",&a[i]); 18 memset(dp,-1,sizeof(dp)); 19 for(i=0;i<n;++i) dp[i][0]=1; 20 printf("%d\n",dfs(0,s)); 21 } 22 return 0; 23 }
poj3480 John (anti-Nim)
题意:n堆石子,两人轮流操作,每次从一堆中取走至少一个。取走最后一个的输。
题解:记个定理:先手必胜有两种状态:
①都是孤单堆(每堆只有一个石头),且SG为0
②至少一堆石子>1(有充裕堆),且SG不为0。
1 #include<cstdio> 2 int main(){ 3 int t,i,n,a,x,ans; 4 scanf("%d",&t); 5 while(t--){ 6 x=ans=0; 7 scanf("%d",&n); 8 while(n--){ 9 scanf("%d",&a); 10 if(a>1) x++; 11 ans^=a; 12 } 13 if(ans==0&&x==0||x>0&&ans!=0) printf("John\n"); 14 else printf("Brother\n"); 15 } 16 return 0; 17 }
poj2425 A Chess Game (SG函数裸题)
题意:一个有向无环图有n个点和m个棋子,两人轮流操作,每次可以将一棋子沿图走一步,无法操作则输。
题解:把每个棋子看作单独的游戏。记忆化搜索dfs,暴力算SG值判断。注意:代码中S数组应为局部变量,如果为全局变量的话,某一步dfs时,发现某个S[i]可能在另一步dfs赋值过,我因为这个WA了好久。(>_<)。
1 #include<cstdio> 2 #include<cstring> 3 using namespace std; 4 const int N=1001; 5 int SG[N],g[N][N]; 6 int n; 7 void dfs(int u){ 8 int i; 9 int S[N]={0}; 10 for(i=0;i<n;++i){ 11 if(g[u][i]){ 12 if(SG[i]==-1)dfs(i); 13 S[SG[i]]=1; 14 } 15 } 16 for(i=0;S[i];++i); 17 SG[u]=i; 18 } 19 int main(){ 20 int i,j,k,m,x,ans; 21 while(scanf("%d",&n)==1){ 22 memset(SG,-1,sizeof(SG)); 23 memset(g,0,sizeof(g)); 24 for(i=0;i<n;++i){ 25 scanf("%d",&x); 26 while(x--){ 27 scanf("%d",&j); 28 g[i][j]=1; 29 } 30 } 31 for(i=0;i<n;++i)if(SG[i]==-1)dfs(i); 32 while(scanf("%d",&m),m){ 33 ans=0; 34 while(m--){ 35 scanf("%d",&i); 36 ans^=SG[i]; 37 } 38 if(ans) puts("WIN"); 39 else puts("LOSE"); 40 } 41 } 42 return 0; 43 }
hdu1536 S-Nim (SG函数裸题)
题意:有n堆石子,两人轮流取,每次可取的石子数目满足给定的集合S,问先手是否必胜。
吐槽:(见代码)bool能AC,int就超时 ̄へ ̄。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 #define N 10001 6 int SG[N],f[101]; 7 int k; 8 bool S[N];//我用int型定义就超时了。。 9 void getSG(){ 10 int i,j; 11 SG[0]=0; 12 for(i=1;i<N;++i){ 13 memset(S,0,sizeof(S)); 14 for(j=0;j<k&&f[j]<=i;++j) 15 S[SG[i-f[j]]]=1; 16 for(j=0;S[j];++j); 17 SG[i]=j; 18 } 19 } 20 int main(){ 21 int i,l,m,ans,h; 22 while(scanf("%d",&k),k){ 23 for(i=0;i<k;++i) scanf("%d",&f[i]); 24 sort(f,f+k); 25 getSG(); 26 scanf("%d",&m); 27 while(m--){ 28 ans=0; 29 scanf("%d",&l); 30 while(l--){ 31 scanf("%d",&h); 32 ans^=SG[h]; 33 } 34 printf("%c",ans?'W':'L'); 35 } 36 puts(""); 37 } 38 return 0; 39 }
【如有错误,敬请指正,欢迎交流】