转载请注明出处,谢谢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;
}