zoj 1039 Number Game

恩,做这道题目是因为有人把它归类到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;;
		}
	}

}


你可能感兴趣的:(算法)