HDU-1079 Calendar Game
题目大意
给定一个 2001/11/04 前的合法日期,每次可以变成下一天,或者变成下一个月的同一天(下个月必须有这一天),两个人轮流变化,问先手是否能必定先到 2001/11/04 ?
Sample Input
3
2001 11 3
2001 11 2
2001 10 3
Sample Output
YES
NO
NO
思路
网上很多通过奇偶判断的都没给出具体解释,在题目的
Discuss
中找到一个很好的解释:解题说明
又看到可以逆推出当前日期的答案,感觉更容易理解,就先写了。
必胜点和必败点的性质:
1、所有终结点是 必败点P。
2、从任何必胜点N 操作,至少有一种方式可以进入 必败点P。
3、无论如何操作,必败点P 都只能进入 必胜点N。
逆推时抓住上面的性质即可推出正确答案。
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n;
int dp[2007][17][37],yy,mm,dd;
const int days[2][13]={{0,31,28,31,30,31,30,31,31,30,31,30,31},
{0,31,29,31,30,31,30,31,31,30,31,30,31}
};
inline int leapYear(int y) {
return (y%4==0&&(y%100!=0||y%400==0))?1:0;
}
bool solve(int y,int m,int d) {
if(dp[y][m][d]!=-1) {
return dp[y][m][d]==1;
}
if(d>days[leapYear(y)][m]) {//日期不合法时,认为当前日期为必胜点
dp[y][m][d]=1;
return true;
}
yy=y;
mm=m+1;
dd=d;
if(mm>12) {
++yy;
mm=1;
}
dp[y][m][d]=(solve(yy,mm,dd)?0:1);//下一个月同一天
if(dp[y][m][d]==0) {
yy=y;
mm=m;
dd=d+1;
if(dd>days[leapYear(yy)][mm]) {
dd=1;
if(++mm>12) {
mm=1;
++yy;
}
}
dp[y][m][d]=(solve(yy,mm,dd)?0:1);//明天
}
return dp[y][m][d]==1;
}
int main() {
int T,y,m,d;
scanf("%d",&T);
memset(dp,-1,sizeof(dp));
for(int i=0;i<=31;++i) {//超过比赛日期则认为是该日期必胜点
dp[2001][11][i]=dp[2001][12][i]=1;
}
dp[2001][11][1]=dp[2001][11][2]=dp[2001][11][3]=-1;
dp[2001][11][4]=0;
while(T-->0) {
scanf("%d%d%d",&y,&m,&d);
printf("%s\n",solve(y,m,d)?"YES":"NO");
}
return 0;
}