ZOJ 1039 Number Game (状态压缩,记忆化搜索)

转载请注明出处,谢谢http://blog.csdn.net/ACM_cxlove?viewmode=contents by---cxlove

题目:2-20这19个数字的游戏。每取走一个数之后,这个数的倍数便不能再取,而且某两个不能取的数的和,也不能再取。

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=39

由于只有19个数,通过状态压缩保存状态,进行记忆化搜索。dp[1<<19]即可,dp[i]表示可行集合为i时的必胜必败情况

接下来重要的是状态转移。

对于原状态state,如果要把数字x从集合中去除的话。首先要分为两个步骤

1、将x以及x的倍数去掉,这便是把一个数的特定位置0的操作,位运算轻松解决,详见代码

2、去除不在集合中的元素与x的倍数的和。这步可以倒过来实现,找到还在集合中的元素,从中减掉若干个x,判断这个新的数是否在集合中,如果不在,说明这个数是非法的,去除。也可以通过位运算解决

在必胜与必败中,N态必然有一个后继是P态,利用这点搞定。

最后要输出可行解,便是枚举删除某个数,判断是否转变为P态即可

#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<vector>
#define C    240
#define TIME 10
#define inf 1<<25
#define LL long long
using namespace std;
int dp[1<<19];
int get_state(int state,int x){
    int ret=state;
    //把x以及x的倍数去除
    //首先把要删除的那位置为1,然后取反,最后与状态相与
    for(int i=x;i<=20;i+=x)
        ret&=~(1<<(i-2));
    for(int i=2;i<=20;i++){
        //如果某个数还在集合当中,则判断是否有某个不在集合中的数和X的倍数组成
        if((1<<(i-2))&ret){
            for(int j=x;i-j-2>=0;j+=x)
                //如果数i为某个不在集合中的数以及x的和,则去除
                if(!((1<<(i-j-2))&ret)){
                     ret&=~(1<<(i-2));
                     break;
                }
        }
    }
    return ret;
}
int get_dp(int state){
    if(dp[state]!=-1)
        return dp[state];
    for(int i=2;i<=20;i++){
        if(state&(1<<(i-2))){
            //将第i个数移出集合
            int tmp=get_state(state,i);
            //P态得到N态
            if(!get_dp(tmp))
                return dp[state]=1;
        }
    }
    return dp[state]=0;
}
int main(){
    memset(dp,-1,sizeof(dp));
    dp[0]=0;
    int t,n,a[20],cas=0;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        int state=0;
        for(int i=0;i<n;i++){
            scanf("%d",&a[i]);
            //初始状态
            state|=1<<(a[i]-2);
        }
        printf("Scenario #%d:\n",++cas);
        if(get_dp(state)==0)
            puts("There is no winning move.\n");
        else{
            printf("The winning moves are:");
            for(int i=0;i<n;i++){
                //枚举去除,是否为必败态
                int tmp=get_state(state,a[i]);
                if(!get_dp(tmp))
                    printf(" %d",a[i]);
            }
            puts(".\n");
        }
    }
    return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值