2021牛客暑期多校训练营1 A:Alice and Bob (筛法+对称优化)

题目链接

题目大意

Alice和Bob做游戏,给两堆石头,各m,n个。游戏中,每一轮必须在某一堆石头中拿去k(k>0)个石头,同时另一堆石头中拿去s*k(s>=0)个石头。最终无法执行该操作的人输掉比赛。现在Alice先手,两人均采取最优策略的情况下,谁会获胜?

分析

考虑采用动态规划的做法来做,定义dp[i][j]表示两堆石头分别是i,j的个数的情况下,先手的人是否获胜,等于1即获胜,等于0失败。容易发现,dp[0][0]=0,因为没得选了。然后,考虑状态的转移。
对于dp[i][j]而言,两堆石头个数为i,j。那么在i那堆里面取1的情况下,j那堆里面可以取0、1、2、3、4、5…i那堆取2的情况下,j那堆有0、2、4、6…
根据可能的选取方式推出下一轮所有的ni,nj,如果下一轮对应的dp[ni][nj]中有0(即下一个轮先手必输),那么说明这种情况下手是可以赢的,则dp[i][j]=1。如果没有0(即无论对于任何选取方法,下一个轮都必定获胜,也就是当前必输),即dp[i][j]=0。

虽然很明显,这个算法时间复杂度上来说是不满足要求的,但是这个算法给了我们启发,那就是1是由0决定的。所以我们考虑,能不能利用筛法来筛出所有的0(话说第一次学会用筛法也是在牛客多校上)。要利用筛法来进行筛除也很简单,如果当前arr[x][y]=0,那么上一轮对应的arr中的位置可以由如下的方式得到:

void forward(int x,int y)
{
	for(int i=1;i+x<=N;i++)//N=5e3
		for(int j=0;j+y<=N;j+=i)
		{
			int ni=i+x,nj=j+y;
			arr[ni][nj]=1;
		}
	for(int i=1;i+y<=N;i++)
		for(int j=0;j+x<=N;j+=i)
		{
			int ni=j+x,nj=i+y;
			arr[ni][nj]=1;
		}
}

我们将所有上一轮更新为1,即先手胜即可。而在主函数当中只需要用以下代码遍历即可筛出所有的0:

for(int i=0;i<=N;i++)
		for(int j=0;j<=N;j++)
			if(arr[i][j]==0)
				forward(i,j);

可能你会问,为什么这样能保证每次筛出来的0就应该是0呢?这是因为我们采取的是从上往下,从左往右遍历。这样,每次遍历到一个位置时,以他为右下角,以(0,0)为左上角的矩形范围内的所有点都做过forward操作了,即能让它变为1的点都被遍历过了,而它还没变成1,所以它必然是0了。

这个算法的时间复杂度相对于dp算法来说要好很多,但还是需要1秒多才能出结果。不过我们还可以利用对称性来进行优化,这样就可以让规模减半。

AC代码:

#include
using namespace std;
const int MAX_N=5e3+5;
bool arr[MAX_N][MAX_N];//用bool比int快
const int N=5e3;
void forward(int x,int y)
{
	for(int i=1;i+x<=N;i++)
		for(int j=0;j+y<=N;j+=i)
		{
			int ni=i+x,nj=j+y;
			if(ni>nj)swap(ni,nj);//对称优化
			arr[ni][nj]=true;
		}
	for(int i=1;i+y<=N;i++)
		for(int j=0;j+x<=N;j+=i)
		{
			int ni=j+x,nj=i+y;
			if(ni>nj)swap(ni,nj);
			arr[ni][nj]=true;
		}
}
int main()
{
	for(int i=0;i<=N;i++)
		for(int j=i;j<=N;j++)//对称优化
			if(arr[i][j]==false)
				forward(i,j);
	int t;
	cin>>t;
	while(t--)
	{
		int m,n;
		cin>>m>>n;
		if(m>n)swap(m,n);
		if(!arr[m][n])cout<<"Bob";
		else cout<<"Alice";
		cout<<endl;
	}
	return 0;
}

最后看了下居然可以直接在代码里面打表,哭死,害我T了好几发

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