恩,做这道题目是因为有人把它归类到dp题中,而最近在专攻dp。这个叫mask dp。不过和我眼中dp的一般算法不太一样。说明我土了。一般dp会先算最小子问题的答案,然后利用小的资问题往大了算,最终得到答案。但是这道题目其实算是打表,就是每当算出一个子问题,就把答案记录下来,以备后用。
然后dp的转化不是很难,主要要想到一点:必赢和必输是互相转化的两个状态(如果当前必赢,那么我采取必赢步骤之后,下一个状态就是必输,反之也一样)。所以对于每个问题,考虑各种数字选择,如果有某个选择可以让接下来的一个状态必输,那么本状态就是必赢了。否则就是必输。
然后考虑怎么存数据,所有的变化有2的19次方之多,不过我们的堆栈还是很大的,有30M。所以我们开一个那么大的数组就行了。然后使用mask将每个状态和数组中的一个元素对应起来。具体看代码吧。
其实这道题目最应该让我自己记住的是位操作:我当时自己想好解决方案之后觉得里面的操作非常复杂,因为我没有很好的方法来处理mask。然后参考了别人的代码之后恍然大悟,发现位操作果然好用,而且效率会搞一些。
好了,贴代码吧
#include<iostream>
#include<cstring>
using namespace std;
const int MAX = (1<<19)-1;
int win[MAX+10];
int getMask(int m, int x)
{
int result = m;
for(int i=x;i<=20;i+=x)
{
result &= ~(1<<(i-2));
}
for(int i=2;i<=20;i++)
{
if( (result >> (i-2)) & 1)
{
for(int j=i-x;j>=2;j-=x)
{
if( !((result >> (j-2)) &1) )
{
result &= ~(1<<(i-2));
break;
}
}
}
}
return result;
}
bool get(int mask)
{
if(win[mask] == 1)
{
return true;
}
else if(win[mask] == 0)
{
return false;
}
for(int i=2;i<=20;i++)
{
if( (mask >> (i-2)) &1)
{
int tmpMask = getMask(mask, i);
if(!get(tmpMask))
{
win[mask] = 1;
return true;
}
}
}
win[mask] = 0;
return false;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("input.txt", "rt", stdin);
freopen("output.txt", "wt+", stdout);
#endif
memset(win, -1, sizeof(win));
for(int i=2;i<=20;i++)
{
win[1<<(i-2)] = 1;
}
int mask = MAX;
int cases;
cin >> cases;
for(int i=1;i<=cases;i++)
{
int no;
cin >> no;
int num;
int mask = 0;
while(no--)
{
cin >>num;
mask |= (1<<(num-2));
}
cout << "Scenario #" << i <<":" << endl;
if(!get(mask))
{
cout << "There is no winning move." << endl << endl;
}
else
{
cout << "The winning moves are:";
for(int j=2 ;j<=20;j++)
{
if( (mask >> (j-2)) & 1)
{
int tmp = getMask(mask, j);
if(!get(tmp))
{
cout << " " << j;
}
}
}
cout << "." << endl << endl;;
}
}
}