1082 Calendar Game

問題概要

スタートの日付から二人のプレイヤーが交互に日付を動かす。
日付は、カレンダーの次の日または、次の月の同じ日にのみ動かすことができる。
次の月に同じ日がない場合は、そこに動かすことはできない。


2001年11月4日に日付を到着させたほうが勝ちのとき、両者が最善の戦略を取ったとして、先手が勝てるかどうかを判定せよ。
開始の日付は1900年1月1日以降である。また、2001年11月4日を超えて日付を移動させた場合には負けになる。

解法

ゴールの日付から逆向きにDPする。
dp[日付]が、その日付で先手番が始まったときに先手が勝てるかとすると、
次の月と次の日のどちらかで、先手が負け(すなわち、自分が勝つ)ならばdp[日付]は1となる。


最初に勝ち負けを全て計算しておくとよい。
閏年や、月の日数がややこしいので注意する。

ソースコード

すんごく見づらい。

int day[]={31,28,31,30,31,30,31,31,30,31,30,31};
bool g[111][12][31];

bool leap(int y,int m){
	return m==1&&y>0&&y%4==0;
}
int main()
{
	for(int y=101;y<103;y++)for(int m=y==101?10:0;m<12;m++)
	for(int d=y==101&&m==10?4:0;d<day[m];d++)g[y][m][d]=1;
	
	g[101][10][3]=0;
	for(int y=101;y>=0;y--)for(int m=y==101?10:11;m>=0;m--)
	for(int d=y==101&&m==10?2:day[m]+leap(y,m)-1;d>=0;d--)
	{
		int yy=y,mm=m,dd=d+1;
		bool nd,nm;
		if(dd>=day[mm]+leap(yy,mm)){
			dd=0;
			if(++mm>=12)mm=0,yy++;
		}
		nd=g[yy][mm][dd];
		
		yy=y; mm=m+1; dd=d;
		if(mm>=12)yy++,mm=0;
		if(day[mm]+leap(yy,mm)<=dd)nm=1;
		else nm=g[yy][mm][dd];
		
		g[y][m][d]=!nd||!nm;
	}
	
	int T; scanf("%d",&T);
	rep(U,T){
		int y,m,d; scanf("%d%d%d",&y,&m,&d);
		y-=1900; m--; d--;
		puts(g[y][m][d]?"YES":"NO");
	}
	return 0;
}