【poj 1143】Number Game 博弈论+记忆化搜索

47 篇文章 0 订阅

参考题解: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;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值