ACM学习-POJ-1143-Number Game

菜鸟学习ACM,纪录自己成长过程中的点滴。

学习的路上,与君共勉。

ACM学习-POJ-1143-Number Game

 

Number Game
Time Limit: 1000MS   Memory Limit: 65536K
Total Submissions: 2914   Accepted: 1162

 

Description

Christine and Matt are playing an exciting game they just invented: the Number Game. The rules of this game are as follows. 
The players take turns choosing integers greater than 1. First, Christine chooses a number, then Matt chooses a number, then Christine again, and so on. The following rules restrict how new numbers may be chosen by the two players: 

  • A number which has already been selected by Christine or Matt, or a multiple of such a number,cannot be chosen. 
  • A sum of such multiples cannot be chosen, either.

If a player cannot choose any new number according to these rules, then that player loses the game. 
Here is an example: Christine starts by choosing 4. This prevents Matt from choosing 4, 8, 12, etc.Let's assume that his move is 3. Now the numbers 3, 6, 9, etc. are excluded, too; furthermore, numbers like: 7 = 3+4;10 = 2*3+4;11 = 3+2*4;13 = 3*3+4;... are also not available. So, in fact, the only numbers left are 2 and 5. Christine now selects 2. Since 5=2+3 is now forbidden, she wins because there is no number left for Matt to choose. 
Your task is to write a program which will help play (and win!) the Number Game. Of course, there might be an infinite number of choices for a player, so it may not be easy to find the best move among these possibilities. But after playing for some time, the number of remaining choices becomes finite, and that is the point where your program can help. Given a game position (a list of numbers which are not yet forbidden), your program should output all winning moves. 
A winning move is a move by which the player who is about to move can force a win, no matter what the other player will do afterwards. More formally, a winning move can be defined as follows. 

  • A winning move is a move after which the game position is a losing position. 
  • A winning position is a position in which a winning move exists. A losing position is a position in which no winning move exists. 
  • In particular, the position in which all numbers are forbidden is a losing position. (This makes sense since the player who would have to move in that case loses the game.)

Input

The input consists of several test cases. Each test case is given by exactly one line describing one position. 
Each line will start with a number n (1 <= n <= 20), the number of integers which are still available. The remainder of this line contains the list of these numbers a1;...;an(2 <= ai <= 20). 
The positions described in this way will always be positions which can really occur in the actual Number Game. For example, if 3 is not in the list of allowed numbers, 6 is not in the list, either. 
At the end of the input, there will be a line containing only a zero (instead of n); this line should not be processed.

Output

For each test case, your program should output "Test case #m", where m is the number of the test case (starting with 1). Follow this by either "There's no winning move." if this is true for the position described in the input file, or "The winning moves are: w1 w2 ... wk" where the wi are all winning moves in this position, satisfying wi < wi+1 for 1 <= i < k. After this line, output a blank line.

Sample Input

2 2 5

2 2 3

5 2 3 4 5 6

0

Sample Output

Test Case #1

The winning moves are: 2



Test Case #2

There's no winning move.



Test Case #3

The winning moves are: 4 5 6

Source


题目要求:

       Christine和Matt玩一个游戏.游戏的规则如下:一开始有一列数字(2~20),有的被列出,有的没有列出.从Christine开始,两人轮流划去一个已被列出的数字.每划去一个数字,其他的所有的能被不在这个数列的数字的两两整线性表示的数也自动从数列中退出.到最后,轮到某人而此时数列中没有元素时,这个人就输了.

       规定:winning moveà能将对手置于losing position的决策,而losing positionà不存在任何winning move的序列.

这两个概念都是在两人都play perfectly 的前提下提出的.


题目分析:

这是一道博弈题,既然要判断先选哪个数一定获胜,那么可首先枚举要选的第一个数,删除该数影响到的数。

那么什么数(设为x)会受该数(设为m)影响呢?分析可知,条件是x-m不在当前可选数列中,也就是说x-m已经选过。

而又选了m,那么由题意可知x必定受到影响要删除。

条件已知。那么剩下的就是根据必胜状态和必败状态的定义搜索这棵博弈树。直接搜索超时,已经试过了。

在网上搜了下,都没有注释。于是找了个代码,啃啊啃,终于明白了。

用的是记忆化搜索。将已给序列从小到大排序。如果用1表示该数在序列中,0表示不在,那么就用它们对应的二进制数来表示

当前面临的状态,如果面临的状态相同且前面已经得出,那么直接返回该结果即可。需要用的两个二进制数,一个用来表示状

态,另一个用来表示该状态下剩余的数(其中1表示不在可选序列中,0表示在)。这样做在删除数的时候非常方便。

下面给出AC代码。

#include<stdio.h>

#include<string.h>



int dp[1<<20];

int info[21];

int n; // 每组的个数



int solve(int a,int b)

{

    int i,j,c,d;

    if( a == 0 )

        return 0;

    if( dp[a] != -1 ) //表明已经判断过了。  直接返回结果

        return dp[a];

    for( i = 0; i < n; i ++ ){

        c = a;

        d = b;

        if( (1 << i) & c ){  //寻找该状态下可选的数,继续搜索

            for( j = 0; j + info[i] < 21; j ++ ){  //标记受该数影响的数

                if( (1 << j) & d ){

                    d = d | (1 << (info[i] + j) );

                }

            }

            for( j = 0; j < n; j ++ ){  //删除受影响的数

                if( ( (1 << j ) & c ) && ( (1 << info[j]) & d ) ){

                    c = c^(1 << j);

                }

            }

            if( !solve(c,d) )

                return dp[a] = 1;

        }

    }

    return dp[a] = 0;

}



int main()

{

    int i, j, k;

    int a,b;   //a表示状态,初始全为1  b表示序列删除的状况,1表示不在,0表示在

    int item;  // 总的序列。  0表示存在,1表示不在

    int res[21];

    int case_ = 1;  //记录测试组数

    while(scanf("%d",&n) != EOF && n!= 0)

    {

        item = (1<<22) - 3;       //将1添进序列,1始终是可选但又不能选的数。总序列变成 11 1111 1111 1111 1111 1101  0指该序列中0存在。

        for( i = 0 ; i < n; i ++ ){

            scanf("%d", &info[i]);

            item ^= (1 << info[i]); //^异或运算符,位值相同为0,不同为1。将序列中的数在item中标记,相应位置都为0,这样做删除方便

        }

        for( i = 0; i < n; i ++ ){ //冒泡排序

            for( j = 0; j < n - i - 1; j ++ ){

                if( info[j] > info[j + 1] ) {

                    info[j] = info[j] ^ info[j + 1];

                    info[j + 1] = info[j] ^ info[j + 1];

                    info[j] = info[j] ^ info[j + 1];

                }

            }

        }

        memset(dp,-1,sizeof(dp));

        for(k = i = 0; i < n;i ++ ){  //枚举第一个被选的数

            a = (1<<n) - 1;   //a表示状态,初始全为1

            b = item;   //b表示序列删除的状况,1表示不在,0表示在

            for( j = 0; j + info[i] < 21; j ++ ){

                if( (1<<j) & b ){  //如果j不在可选序列,那么j + info[i] 会因info[i]的删除而删除

                    b = b | ( 1 << (j + info[i]) );  //将该数相应位置置1

                }

            }

            for( j = 0; j < n; j ++ ){  //判断哪个数被删除了

                if( (1 << info[j]) & b ){

                    a = a ^ (1 << j);  //将该数删除置为0

                }

            }

            if( !solve(a,b) )

                res[k ++] = info[i];

        }

        printf("Test Case #%d\n",case_ ++);

        if( k ) {

            printf("The winning moves are:");

            for( i = 0; i < k; i ++ ){

                printf(" %d",res[i]);

            }

            puts("");

        }

        else puts("There's no winning move.");

        puts("");

    }

    return 0;

}


附上几个参考链接  http://happylch21.blog.163.com/blog/static/165639759201162243858792/
http://blog.csdn.net/zhengweihit/article/details/5799427

 

你可能感兴趣的:(number)