[CSP-S模拟测试]:取石子(博弈论+DP)

题目描述

有三堆石子,它们的石子个数分别为$x,y,z$。
$A$和$B$正在博弈,由$A$先手,双方轮流操作。
每次操作是指,选择若干堆($1-3$堆)石子,从中各取出相同数量的石子(不能$1$个都不取)。不能操作的人失败。
请判定是否先手必胜。


输入格式

第一行一个整数$T$,表示数据组数。
接下来$T$行,每行三个整数$x,y,z(1\leqslant x,y,z\leqslant 300)$,描述一组数据。


输出格式

每组数据输出一行:
$\bullet$若先手必胜,输出$Yes$,否则输出$No$


样例

样例输入:

2
1 1 1
1 2 3

样例输出:

Yes
Yes


数据范围与提示

样例解释:

第一组数据,先手可以一次把所有石子取完。

第二组数据,先手第一步可以取完第三堆石子,得到$(1,2,0)$是一个先手必败的局面,从而刚开始的先手必胜。

数据范围:

对$100\%$的数据,$T\leqslant 500$,记$M=max(x,y,z)$。
$\bullet$子任务$1$($10$分):保证$M\leqslant 7$。
$\bullet$子任务$2$($30$分):保证$M\leqslant 50$。
$\bullet$子任务$3$($30$分):保证$\min(x,y,z)=0$。
$\bullet$子任务$4$($30$分):保证$M\leqslant 300$。


题解

这是一个$DP$……

首先,设$dp[i][j][k]$表示第一堆有$i$个,第二堆有$j$个,第三堆有$k$个是否必胜。

根据博弈论思想,如果一个局面可以转移为一个必败局面,那么这个局面必胜;注意反之则不然,因为我们可以不转移向必胜的局面。

初始时将所有局面都置为负,然后从小到大枚举$i,j,k$,如果当前局面没有标记胜,则一定为负,然后将所有能转移到它的局面置为胜即可。

看似时间复杂度是$\Theta(n^4)$的,但是注意只有在负的情况下我们才枚举所有能转移到它的局面,而负的局面只有$64972$,所以还是能很快的跑过去的。

时间复杂度:$\Theta(n^3+64972\times n+T)$。

期望得分:$100$分。

实际得分:$100$分。


代码时刻

#include<bits/stdc++.h>
using namespace std;
char ans[301][301][301];
void pre_work()
{
	int res;
	for(int i=0;i<=300;i++)
		for(int j=0;j<=300;j++)
			for(int k=0;k<=300;k++)
			{
				if(ans[i][j][k])continue;
				for(int l=i+1;l<=300;l++)ans[l][j][k]=1;
				for(int l=j+1;l<=300;l++)ans[i][l][k]=1;
				for(int l=k+1;l<=300;l++)ans[i][j][l]=1;
				res=300-max(i,j);
				for(int l=1;l<=res;l++)ans[i+l][j+l][k]=1;
				res=300-max(i,k);
				for(int l=1;l<=res;l++)ans[i+l][j][k+l]=1;
				res=300-max(j,k);
				for(int l=1;l<=res;l++)ans[i][j+l][k+l]=1;
				res=300-max(i,max(j,k));
				for(int l=1;l<=res;l++)ans[i+l][j+l][k+l]=1;
			}
}
int main()
{
	pre_work();
	int T;scanf("%d",&T);
	while(T--)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		puts(ans[x][y][z]?"Yes":"No");
	}
	return 0;
}

rp++

posted @ 2019-10-26 16:12  HEOI-动动  阅读(437)  评论(0编辑  收藏  举报