这个题目类似之前看过的一道取石子的题目,就是必胜局面下一步会出现必输局面,必输局面下一步会出现必胜局面,因为是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,结果当然也可以做出来,
但是我自己就觉得好蠢。
所以上网看了看别人的代码...
只用了一层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;
}