POJ 1143 (记忆化搜索,状态压缩,博弈论记忆化搜索实现)

来源:互联网 发布:java安装教程 编辑:程序博客网 时间:2024/06/08 14:09

Number Game
Time Limit: 1000MS Memory Limit: 65536KTotal Submissions: 3760 Accepted: 1557

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 52 2 35 2 3 4 5 60

Sample Output

Test Case #1The winning moves are: 2Test Case #2There's no winning move.Test Case #3The winning moves are: 4 5 6


题意:

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

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

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


       首先还是解释一下什么是线性表示的数,假设a,b是2~20里未被列出的数其中两个,k是列出的数之一,   如果存在a*n+b*m=k  (n,m>=0),

则 k 需要退出。


分析:


几个首先想到的明显的结论:


      (1) 数列中必定有2和3其中的至少一个,也即2和3可以整线性表示出后面所有的数.由此可推出:如果初始的数列只有2(3),那么此时的winning move只有一个:2(3).

       (2) 如果2和3都同时出现在序列中,在所有其他元素被取完之前, Christine和Matt是不敢碰2或者3的,因为一旦有个人取了其中的一个数,问题就回到了(1),这个人必败.此种情况下任何一个先取2(3)的人都是被逼迫的.所以这种情况可只考虑3之后的数,但这个结论对此题的优化似乎没什么明显的帮助。


所以我们想到枚举所有情况,然后标记这个情况成功状态还是失败,标记情况可以用2进制状态压缩。然后开个最多2^20大的数组存下所有情况。

还有就是当划去一个值时,怎样判断列出的值中需要退出的数,解决思路就是完全背包,每划去一个数就相当于完全背包多了一个物品。


记忆化搜索+状态压缩


代码:

#include <iostream>#include <cstdio>#include <cstring>#include <cmath>#include <algorithm>#include <stack>#include <map>#include <set>#include <vector>#include <queue>#define mem(p,k) memset(p,k,sizeof(p));#define rep(a,b,c) for(int a=b;a<c;a++)#define pb push_back#define lson l,m,rt<<1#define rson m+1,r,rt<<1|1#define inf 0x6fffffff#define ll long longusing namespace std;int m,n,minn,maxx,k,cur;int dp[600000],num[30],ans[50];int getnum(int num[30]){    int sum=0,f=1;    for(int i=2;i<=20;i++,f<<=1){        if(num[i])sum+=f;    }    return sum;}int dfs(int *a,int dig){    int b[30];    for(int i=2;i<=20;i++)b[i]=a[i];    b[dig]=0;    for(int i=2;i+dig<=20;i++){//判断应该退出的数,完全背包        if(!b[i]){            b[i+dig]=0;        }    }    int newnum=getnum(b);    if(newnum==0)return 0;    if(dp[newnum])return dp[newnum]>0?1:0;    for(int i=2;i<=20;i++){        if(b[i]&&!dfs(b,i)){            return dp[newnum]=1;        }    }    dp[newnum]=-1;    return 0;}int main(){    cur=1;    mem(dp,0);    while(cin>>n&&n){        mem(num,0);        for(int i=0;i<n;i++){            scanf("%d",&k);            num[k]=1;        }        int le=0;        for(int i=2;i<=20;i++){            if(num[i]&&!dfs(num,i)){                ans[le++]=i;            }        }        cout<<"Test Case #"<<cur++<<endl;        if(le==0){            cout<<"There's no winning move."<<endl<<endl;continue;        }        cout<<"The winning moves are: ";        sort(ans,ans+le);        for(int i=0;i<le;i++){            printf("%d",ans[i]);            if(i==le-1)cout<<endl<<endl;            else cout<<" ";        }    }    return 0;}


0 0