参考题解:http://blog.csdn.net/u013382399/article/details/39544863
把题意抽象一下,大概是以下意思:
1.给2~20的数字中的几个数组成数组a,其中是你可以选择的数字;
2.选择的规则如下:
(1).如果选择了某个数字x,则数组a中是其倍数的数字将被划去;
(2).假如数字n在数组a中,若n-x 的值并不在数组a中,则划去n;
3.当没有数字可以选取时,则此player失败;
4.让你找出先选的player第一次选哪个数字就会胜利,输出第一次选择的数字(如果有多个答案就全部输出)。若没有选择输出“(所给的一句话)”
思路:
我的第一道博弈论。以前有看过关于sg函数的简介无奈没做过相似的题所以收货不大,正好不上;
首先我们应该思考的就是状态,即从当前状态出发,如果双方都足够聪明就一定会成功或者一定失败。这个就类似于dp所以想到记忆化搜索,定义f[pos]表示在pos这个状态下的必然结果,然后用递归每一次模拟取数的过程,具体参考代码。
一些疑问:
1.为什么可以记忆化?因为只要两个人足够聪明,取数的顺序就不会变,满足无后效性
2.check函数为什么用(1<<(cur[j]-cur[i]))&(~state)而不直接(1<<(cur[j]-cur[i]))^(state)?其实咋一开是有那么点道理,因为的确在那一位的答案是一致的,但是考虑一个事实就是^运算如果1<<i位后面全是0,而0^1同样是1,会影响答案,所以要先取反再&
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define maxn 1<<21+1
#define clear(x) memset(x,0,sizeof(x))
using namespace std;
int n,f[maxn],cur[25],ans[22];
bool check(int i,int j,int state){
return ((1<<(cur[j]-cur[i]))&(~state)&&(cur[j]-cur[i]!=1))||(cur[j]%cur[i]==0);//这里必须是先~然后再&不可以^ 整除的话也需要直接取出
}
int dfs(int k,int state){
if(f[state]!=-1)return f[state];//如果搜索过这一个状态就不再向下搜索
if(state==0)return f[state]=0;//如果没有数可以选了就是输了
//if(k==1)return f[state]=1;//如果只用选 一个就赢了,但是其实可以不要这句话
for(int i=1;i<=n;i++){//枚举现在选哪一个数
int st=state;
if((st & (1<<(cur[i])))==0)continue;//如果当前状态没有这个数自然continue
for(int j=i+1;j<=n;j++)if(check(i,j,st)&&(st&(1<<cur[j])))st^=(1<<cur[j]);//删去这个数以后还有删去一些
if(dfs(k-1,st^(1<<cur[i]))==0)return f[state]=1;//如果选了这个数以后后面就不能选了,当前为胜利状态
}
return f[state]=0;//后面都有选的失败
}
int main(){
int cas=0;
while(scanf("%d",&n)!=EOF&&n){
cas++;//组数增加
int state=0,cnt=0;
clear(cur);clear(ans);//初始化
memset(f,-1,sizeof(f));
for(int i=1;i<=n;i++){
scanf("%d",cur+i);
state|=(1<<cur[i]);//定义一开始的状态那些数有那些数没有
}
sort(cur+1,cur+1+n);//从小到大排序,方便后面的查找
for(int i=1;i<=n;i++){//开始枚举直接第一个取出哪一个数必赢
int now=state;//复制状态
for(int j=i+1;j<=n;j++)//选取这一个数之后那些数将会被删去
if(check(i,j,now)){//如果这个数满足条件
now^=1<<cur[j];//将这个数从状态中取出
}
if(dfs(n-1,now^(1<<cur[i]))==0){//如果从当前状态出发不能获胜那么当前状态就会获胜
ans[++cnt]=cur[i];//记录答案
}
}
//输出
printf("Test Case #%d\n",cas);
if(cnt==0)printf("There's no winning move.\n\n");
else{
printf("The winning moves are:");
for(int i=1;i<=cnt;i++)printf(" %d",ans[i]);
printf("\n\n");
}
}
return 0;
}