POJ1143 Number Game 动态规划

这个题目类似之前看过的一道取石子的题目,就是必胜局面下一步会出现必输局面,必输局面下一步会出现必胜局面,因为是DP题,所以还是按照DP的方法做,对于一个包含数字x1,x2,x3...,xn的局面,如果存在任意的i,使得,x1,x2...,xi-1,xi+1,...,xn成为一个必输的局面,那么x1,x2,x3...,xn就是一个必胜的局面,从而这些满足条件的i就是取数字的策略,这样对局面的判定逐层嵌套,再默认只有一个数字时为必胜局面,0个数字时为必输局面,就可以求得结果了,但是这样做会导致重复搜索比如,先取掉xi,再取xj和先取xj再取xi将会导致两次重复的搜索,所以只要把每个局面的搜索结果储存在数组里就可以了。

因为只有19个数字,所以可以用二进制编码,一个19位二进制数字可以对应一个局面,如果数字i在当前局面内,则第i-1位为1,因为数字从2开始,所以是i-1位,最大是20,所以一共有19位,从而开辟一个2^19=524288的数组储存就行了,再对策略进行相同的编码,一个策略对应一个19位二进制数字,如果取数字i可以必胜,就令第i-1位为1,将之转化为十进制数字储存在开辟的524288维数组中,其索引就是这种策略对应的局面,从而问题得到了解决。

然而一开始我在删除数字的操作中犯傻使用了三层for循环,结果网上看别人的代码一层for循环就可以了,本来时间是110ms,一改变成了16ms,

我本来去掉因为取出数字而受到影响的数字时,是这样写的:

对每个局面中的数字j,取出的数字i,如果满足

(j-k*i)%l=0对于任意满足j-k*i>0以及不属于局面中的某个数字l(2<=l<=20)成立,则j是一个受影响的数字,所以去掉数字j,

这个想法不就是源于所有

m*l+k*i的数字都要被去掉嘛,

我用了三重循环遍历了这个j k l,结果当然也可以做出来,

但是我自己就觉得好蠢。

所以上网看了看别人的代码...

扫描二维码关注公众号,回复: 3276256 查看本文章

只用了一层for循环是这样的

对于k=2...20

如果k不在当前局面内,则将k+i剔除,当然k+i<=20。

这样就可以了????

想了想的确可以,

首先i的倍数,当k=i时,2i会被剔除,k=2i时,3i会被剔除

其次i和不在局面内的元素t的线性组合mi+nt

只要(m-1)*i+nt不在局面内t就行,

若(m-1)*i+nt不在局面内,

那么(m-2)*i+nt不在局面内,

....

如此反复,只要nt不在局面内就行,

nt不在局面内是显然的,因为t本身不在局面内,然后nt又是t的倍数,题目也写明了,不会给出不可能存在的局面比如1 5,而5=2+3这个局面是不可能存在的

AC的那一刻我十分激动,当时那题一整面的提交记录都是我的wrong answer 和time limit excceeded...(是这么拼的嘛...)

代码如下,在层与层之间的交接,我采用的是十进制的数组输入,也许这是导致内存2000多k的原因,之前看了别人的一个代码,传的是二进制的标记,内存在600k左右,其他方面我感觉差不多,试着改了一次二进制,但是太麻烦,不如重写,然后,就懒得写了~~~

#include<iostream>
using namespace std; 
int timemark[21];        //每次搜索时,对数字是否可选的标记 
int N;					//输入数字的个数 
int a[21];				//储存输入的数字 
int record[524288];		//保存局面策略的数组 
int cifang(int m,int n){//求m的n次方 
	if(n==0)
	return 1;
	int s=m;
	for(int i=1;i<n;i++)
		s=s*m;
	return s;
}
int bin2dec(int c[],int n){//将数组c对应的局面2进制数转化为十进制数字 
	int s=0;
	for(int i=1;i<=n;i++){
		s=s+cifang(2,c[i]-2);
	}
	return s;
}
int pd(int a[],int n){	//a是储存当前局面数字的数组,n是数组的长度 
	int b2d=bin2dec(a,n);	//求这个局面对应的十进制索引 
	if(record[b2d]!=0){		//如果是已搜索过的局面,则直接返回结果 
		return record[b2d];
	}
	else{//否则对这个局面开始搜索 
		int winmove[21];//储存胜利策略的数组 
		int winCount=1;//储存胜利策略的数量 
			int flag=0;//标记是否为必胜局面的变量,0必输,1必胜 
			for(int i=1;i<=n;i++){//循环取出a中第i个数字 
				int count=1;//取出数字的个数 
				for(int ii=2;ii<=20;ii++){//标记若数字t在数组a中,则标记为1,否则为0,去掉第i个元素,所以timemark[i]=0 
					timemark[ii]=0;
				}
				for(int ii=1;ii<=n;ii++){
					if(ii==i)
					continue;
					timemark[a[ii]]=1;
				}	
				for(int k=2;a[i]+k<=20;k++){//将因为第i个数字被取出而影响的数字剔除 
						if(timemark[k]==0&&timemark[a[i]+k]!=0){
							count++;
							timemark[a[i]+k]=0;
						}
				}
				int b[21];//储存a去掉取出的数字和受影响的数字后的数组,用来进行下一层搜索 
				for(int indb=1;indb<=n-count;){
					for(int inda=1;inda<=n;inda++){
						if(timemark[a[inda]]==1){
							b[indb]=a[inda];
							indb++;
						}
					}
				}
				int next=pd(b,n-count);//求下一层局面的胜负情况 
				if(next==-1){//如果下一层为必输局面,这一层就是必胜局面,同时当前取出的数字成为一个必胜策略 
					winmove[winCount++]=a[i];
					flag=1;
				}
				if(i==n&&flag==0){//遍历本次所有可取的数字后,记录输赢的标记flag为0,则说明无论取哪个数字,下一个局面都是一个必胜局面,从而当前局面为必输局面 
					record[bin2dec(a,n)]=-1;
					return -1;
				}
				else if(i==n&&flag==1){//遍历本次所有可取的数字后,记录输赢的标记flag为0,从而当前局面为必赢局面 
					record[bin2dec(a,n)]=bin2dec(winmove,winCount-1);//将必胜策略转化为十进制数字储存在record中 
					return 1;
				}
				}
			}
			
		}
void printWin(int t){//打印必胜时的策略 
	int count = 2;
	while(t!=0){
		if(t%2==1)
			cout<<count<<' ';
		count++;
		t=t/2;
	}
	cout<<endl;
}
int main(){
	cin>>N;
	int count=1;
	record[0]=-1;
	for(int i=1;i<524288;i++){
		record[i]=0;
	}
	for(int i=2;i<=20;i++){
		int a[2];
		a[1]=i;
		record[bin2dec(a,1)]=bin2dec(a,1);
	}
	while(N){
		for(int i=1;i<=N;i++)
		cin>>a[i];
		cout<<"Test Case #"<<count++<<endl;
		if(N==1){
			cout<<"The winning moves are: "<<a[1]<<endl;
		}
		else{
			int t=pd(a,N);
			if(t==-1)
				cout<<"There's no winning move."<<endl;
			else{
				cout<<"The winning moves are: ";
				printWin(record[bin2dec(a,N)]);
			}
		}
		cout<<endl;
		cin>>N;
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/luo3300612/article/details/78208007