找出游戏的必胜策略

本文来自《挑战程序设计竞赛》4.2找出游戏的必胜策略

1.游戏与必胜策略

1.硬币游戏1

1.题目原文

Alice和Bob在玩这样一个游戏:给定k个数字a[1],a[2],……a[k],一开始,有x枚硬币,Alice和Bob轮流取硬币。每次所取硬币的个数一定要在a[1],a[2]……a[k]中。Alice先取,取走最后一枚硬币的人获胜。当双方都采取最优策略时,谁会获胜?题目假定a[1],a[2],……a[k]中一定有1。

1<=x<=100000,1<=k<=100,1<=a[i]<=x

2.解题思路

下面考虑轮到自己时,还有j枚硬币的情况:
i)题目规定取走最后一枚硬币的人获胜,所以j=0是必败态;
ii)若存在i满足1<=i<=k,j-a[i]是必败态,那么j就是必胜态(只需取走a[i]枚硬币即可);
iii)若对于任意i(1<=i<=k),j-a[i]都是必胜态,则j是必败态(无论怎么取,对方都会赢)。
根据上述规则,就可以利用动态规划算法按照j从小到大的顺序计算必胜态和必败态。只要看x是必胜态还是必败态即可。

3.代码

/*Case
X K
a[1] a[2] ……a[K]
Case 1:
9 2
1 4

Alice

Case 2:
10 2
1 4
Bob
*/
#include <iostream>
#include<cstdio>
using namespace std;
const int MAX_K=105;
const int MAX_X=10005;
int X,K,a[MAX_K];
bool win[MAX_X];
void solve()
{
    win[0]=false;
    for(int j=1;j<=X;j++){
        win[j]=false;
        for(int i=0;i<K;i++){
            win[j]|=a[i]<=j&&!win[j-a[i]];
        }
    }
    if(win[X]) puts("Alice");
    else puts("Bob");
}
int main()
{
    while(cin>>X>>K){
        for(int i=0;i<K;i++){
            cin>>a[i];
        }
        solve();
    }
    return 0;
}

2.A Funny Game

1.题目原文


Language:
A Funny Game
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 5290 Accepted: 3304

Description

Alice and Bob decide to play a funny game. At the beginning of the game they pick n(1 <= n <= 10 6) coins in a circle, as Figure 1 shows. A move consists in removing one or two adjacent coins, leaving all other coins untouched. At least one coin must be removed. Players alternate moves with Alice starting. The player that removes the last coin wins. (The last player to move wins. If you can't move, you lose.) 
 
Figure 1

Note: For n > 3, we use c1, c2, ..., cn to denote the coins clockwise and if Alice remove c2, then c1 and c3 are NOT adjacent! (Because there is an empty place between c1 and c3.) 

Suppose that both Alice and Bob do their best in the game. 
You are to write a program to determine who will finally win the game.

Input

There are several test cases. Each test case has only one line, which contains a positive integer n (1 <= n <= 10 6). There are no blank lines between cases. A line with a single 0 terminates the input. 

Output

For each test case, if Alice win the game,output "Alice", otherwise output "Bob". 

Sample Input

1
2
3
0

Sample Output

Alice
Alice
Bob

Source

POJ Contest,Author:Mathematica@ZSU

2.解题思路

首先考虑能把所有的硬币分成两个完全相同的组的状态是必胜态还是必败态?事实上,这是必败态。无论自己采取什么策略,对方只要在另一组中采取相同的策略,就回到了两个相同的组的状态。不断循环下去,总会在某次轮到自己时没有硬币了而败北。
现在回到正题,Alice在第一步取走一枚或者两枚硬币,原来的硬币圈变成了n-1或者n-2的链,Bob只需根据奇偶性从中间选取一枚或两枚硬币,就能分成两组完全相同的硬币。根据上述分析,Alice必败。而n<=2时Alice可以在第一步就取完所有硬币。
在这类游戏中,作出对称的状态后再完全模仿对手的策略常常是有效的。

3.AC代码

#include <iostream>
#include<cstdio>
using namespace std;
int main()
{
    int n;
    while(cin>>n&&n){
        if(n<=2){
            cout<<"Alice"<<endl;
        }
        else cout<<"Bob"<<endl;
    }
    return 0;
}

3.Euclid's Game

1.题目原文


Euclid's Game
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 8958 Accepted: 3663

Description

Two players, Stan and Ollie, play, starting with two natural numbers. Stan, the first player, subtracts any positive multiple of the lesser of the two numbers from the greater of the two numbers, provided that the resulting number must be nonnegative. Then Ollie, the second player, does the same with the two resulting numbers, then Stan, etc., alternately, until one player is able to subtract a multiple of the lesser number from the greater to reach 0, and thereby wins. For example, the players may start with (25,7): 
         25 7

         11 7

          4 7

          4 3

          1 3

          1 0

an Stan wins.

Input

The input consists of a number of lines. Each line contains two positive integers giving the starting two numbers of the game. Stan always starts.

Output

For each line of input, output one line saying either Stan wins or Ollie wins assuming that both of them play perfectly. The last line of input contains two zeroes and should not be processed.

Sample Input

34 12
15 24
0 0

Sample Output

Stan wins
Ollie wins

Source

2.解题思路

记录状态为(a,b)(默认a<b,如果a>b,则交换a,b)。如果b是a的倍数,则是必胜态。如果b不是a的倍数,则可以分为两种情况。
i)b-a<a;
ii)b-a>a。
第一种b-a<a,状态(a,b)只能转移到(a,b-a)。没有其余的选择余地。
第二种情况,假设x是使b-ax<a的整数。0<b-a(x-1)-a<a,考虑(a,b-a(x-1)),如果(a,b-a(x-1))是必败态,则(a,b)是必胜态,因为(a,b)可以一步转到(a,b-a(x-1))。如果(a,b-a(x-1))是必胜态,而状态(a,b-ax)是(a,b-a(x-1))唯一可以转移到的状态,所以(a,b-ax)是必败态,那么(a,b)是必胜态,所以第二种情况(a,b)是必胜态。

3.AC代码

#include <iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int a,b;
void solve()
{
    bool ok=true;
    for(;;){
        if(a>b) swap(a,b);
        //b是a的倍数时,必胜
        if(b%a==0) break;
        //是解说中的第二种情况
        if(b-a>a) break;
        b-=a;
        ok=!ok;
    }
    if(ok) puts("Stan wins");
    else puts("Ollie wins");
}
int main()
{
    while(cin>>a>>b){
        if(a==0&&b==0) break;
        solve();
    }
    return 0;
}

2.Nim

1.Nim

1.题目原文

有n堆石子,每堆各有a[i]个。Alice和Bob轮流从非空的石子堆中取走至少一个石子。Alice先取,取光所有石子的一方获胜。当双方都采取最优策略时,谁会获胜?
限制条件:1<=1000000,1<=a[i]<=10^9.

2.解题思路

这个游戏是称为Nim的经典游戏,该游戏的策略也成为许多游戏的基础。要判断该游戏的胜负,只要用异或运算就行了。有以下结论成立:
a[1]^a[2]^a[3]……^a[n]!=0→ 必胜态。
a[1]^a[2]^a[3]……^a[n]=0→ 必败态.。
下面进行简略证明,具体的解释及证明,可参见 Nim游戏-百度百科
首先证明,必败态只能转移到必胜态。假设必败态a[1]^a[2]^a[3]……^a[n]=0可以转移到必败态,存在i,a'[i]使得a[1]^a[2]^……a'[i]^……^a[n]=0,则根据异或运算的规则,可以得出a[i]=a'[i],题目要求至少取一个石子,不可能,所以必败态只能转移到必胜态。
其次证明,必胜态可以转移到必败态。记必败态a[1]^a[2]^a[3]……^a[n]=k,k的二进制的最高位必是1,则存在i(1<=i<=n)满足在k的二进制的最高位处a[i]的二进制必是1(否则所有的a[i]全为0,异或结果不可能是1),则a[i]^k<a[i],记为a'[i],则有a[1]^a[2]^……^a'[i]^……^a[n]=k^k=0,这是必败态。所以必胜态可以转到必败态。
证明完毕。

3.代码

/*
n
a[1] a[2]  a[n]
Case 1:
3
1 2 4

Alice

Case 2:
3
1 2 3

Bob
*/
#include <iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAX_N=1000005;
int n;
int a[MAX_N];
void solve()
{
    int x=0;
    for(int i=0;i<n;i++){
        x^=a[i];
    }
    if(x!=0) puts("Alice");
    else puts("Bob");
}
int main()
{
    while(cin>>n&&n){
        for(int i=0;i<n;i++){
            cin>>a[i];
        }
        solve();
    }
    return 0;
}

2.Georgia and Bob

1.题目原文

Georgia and Bob
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 9487 Accepted: 3108

Description

Georgia and Bob decide to play a self-invented game. They draw a row of grids on paper, number the grids from left to right by 1, 2, 3, ..., and place N chessmen on different grids, as shown in the following figure for example: 

Georgia and Bob move the chessmen in turn. Every time a player will choose a chessman, and move it to the left without going over any other chessmen or across the left edge. The player can freely choose number of steps the chessman moves, with the constraint that the chessman must be moved at least ONE step and one grid can at most contains ONE single chessman. The player who cannot make a move loses the game. 

Georgia always plays first since "Lady first". Suppose that Georgia and Bob both do their best in the game, i.e., if one of them knows a way to win the game, he or she will be able to carry it out. 

Given the initial positions of the n chessmen, can you predict who will finally win the game? 

Input

The first line of the input contains a single integer T (1 <= T <= 20), the number of test cases. Then T cases follow. Each test case contains two lines. The first line consists of one integer N (1 <= N <= 1000), indicating the number of chessmen. The second line contains N different integers P1, P2 ... Pn (1 <= Pi <= 10000), which are the initial positions of the n chessmen.

Output

For each test case, prints a single line, "Georgia will win", if Georgia will win the game; "Bob will win", if Bob will win the game; otherwise 'Not sure'.

Sample Input

2
3
1 2 3
8
1 5 6 7 9 12 14 17

Sample Output

Bob will win
Georgia will win

Source

2.解题思路

如果把棋子两两成对当做整体考虑,我们可以把这个游戏转为Nim游戏。按棋子个数的奇偶分情况讨论。首先,考虑棋子个数为偶数的情况,把棋子从前往后两两组队,把每对棋子看成Nim游戏里的一堆石子,石子堆中石子的个数等于两个棋子之间的间隔。
接下来考虑为什么可以这样转换。考虑其中某对棋子,将右边的棋子向左移动就相当于从Nim游戏的石子堆中取出石子。另一方面,将左边的棋子向左移动,石子的数量就增加了,这与Nim游戏不同。但是即便对手增加了石子的数量,只要将所增加部分减回去即可;即便自己增加了石子的数量,对手只要减回去就可回到原来的状态。因此,该游戏的胜负状态和所转移的Nim游戏的胜负状态是一致的。
当棋子的个数为奇数时,把最左边的棋子与坐标为0的棋子看成一对(那个棋子假想的),同样可以转为Nim。

3.AC代码

#include <iostream>
#include<algorithm>
using namespace std;
const int MAX_N=1005;
int n;
int p[MAX_N];
void solve()
{
    if(n&1) p[n++]=0;
    sort(p,p+n);
    int x=0;
    for(int i=0;i+1<n;i+=2){
        x^=p[i+1]-p[i]-1;
    }
    if(x==0){
        cout<<"Bob will win"<<endl;
    }
    else{
        cout<<"Georgia will win"<<endl;
    }
}
int main()
{
    int t;
    cin>>t;
    while(t--){
        cin>>n;
        for(int i=0;i<n;i++){
            cin>>p[i];
        }
        solve();
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值