【ZOJ3939 The 13th Zhejiang Provincial Collegiate Programming ContestD】【日期前缀和预处理】The Lucky Week 第n个幸运

The Lucky Week

Time Limit: 2 Seconds      Memory Limit: 65536 KB

Edward, the headmaster of the Marjar University, is very busy every day and always forgets the date.

There was one day Edward suddenly found that if Monday was the 1st, 11th or 21st day of that month, he could remember the date clearly in that week. Therefore, he called such week "The Lucky Week".

But now Edward only remembers the date of his first Lucky Week because of the age-related memory loss, and he wants to know the date of the N-th Lucky Week. Can you help him?

Input

There are multiple test cases. The first line of input is an integer T indicating the number of test cases. For each test case:

The only line contains four integers Y, M, D and N (1 ≤ N ≤ 109) indicating the date (Y: year, M: month, D: day) of the Monday of the first Lucky Week and the Edward's query N.

The Monday of the first Lucky Week is between 1st Jan, 1753 and 31st Dec, 9999 (inclusive).

Output

For each case, print the date of the Monday of the N-th Lucky Week.

Sample Input
2
2016 4 11 2
2016 1 11 10
Sample Output
2016 7 11
2017 9 11

Author: GAN, Tiansheng
Source: The 13th Zhejiang Provincial Collegiate Programming Contest

#include<stdio.h> 
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b>a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b<a)a = b; }
const int N = 0, M = 0, Z = 1e9 + 7, ms63 = 0x3f3f3f3f;
int casenum, casei;
int mon[13] = { 0,31,0,31,30,31,30,31,31,30,31,30,31 };
struct date
{
	int y, m, d;
	date(int y, int m, int d) :y(y), m(m), d(d) {}
};
int tp[400][13][32];	//tp[y][m][d]表示如果y年m月d日是满足要求的日期,那么0年1月1日是星期几
int o[400][13][32];		//o[y][m][d]表示如果y年m月d日是满足要求的日期,那么这是其在相应起点下所对应的第几个满足要求的日期
vector<date>a[7];		//a[i][j]表示0年1月1日是星期i的条件下的第j个满足要求的日期
int nxt[7] = { 1,2,3,4,5,6,0 };
int sum[7];				//sum[i]表示0年1月1日是星期i的条件下,在400年范围内有几个满足要求的日期
bool leap(int y)
{
	return y % 400 == 0 || y % 100 != 0 && y % 4 == 0;
}
void init()
{
	for (int st = 0; st < 7; ++st)
	{
		int w = (st + 6) % 7;
		sum[st] = 0;
		for (int y = 0; y < 400; ++y)
		{
			mon[2] = leap(y) + 28;
			for (int m = 1; m <= 12; ++m)
			{
				for (int d = 1; d <= mon[m]; ++d)
				{
					w = nxt[w];
					if ((d == 1 || d == 11 || d == 21) && w == 0)
					{
						tp[y][m][d] = st;
						o[y][m][d] = sum[st]++;
						a[st].push_back(date(y, m, d));
					}
				}
			}
		}
	}
}
int main()
{
	init();
	scanf("%d", &casenum);
	for (casei = 1; casei <= casenum; ++casei)
	{
		int y, m, d, n;
		scanf("%d%d%d%d", &y, &m, &d, &n);
		
		int cir = y / 400; y %= 400;
		int typ = tp[y][m][d];

		n += o[y][m][d] - 1;
		cir += n / sum[typ]; n %= sum[typ];

		int yy = cir * 400 + a[typ][n].y;
		int mm = a[typ][n].m;
		int dd = a[typ][n].d;
		printf("%d %d %d\n", yy, mm, dd);
	}
	return 0;
}
/*

【题意】
我们定义幸运日为——
1,星期1,
2,恰好是当前月份的1号,11号或21号。

现在给你
(1st Jan, 1753 and 31st Dec, 9999)范围内的某个日期,
把该日期作为第一个幸运日,问你第n(1e9范围)个星期日是几年几月几日

【类型】
前缀和预处理
暴力模拟

【分析】
没有告诉你数据组数。而且N如此之大,直接暴力要GG。

首先,对于日期天数,显然400年构成一个循环。
即400年前的几月几号星期几,现在同样是星期几。
于是我们直接枚举0年1月1日星期几,然后暴力处理400年的前缀和。

那么——
对于当前的y年m月d日,
我们把y/400得到轮数(400年为一轮)偏移量,
把y%=400得到日期映射,

然后增加的n-1天,将(n-1)/sum转化为轮数的偏移量
用a[y][m][d]记录y年m月d日是400年内第几个幸运日,并+=(n-1)%sum 映射得到400年一轮下,轮数之外的偏移量。
具体参考代码。

【时间复杂度&&优化】
O(400*365)

一个优化是,我们可以只做400年的预处理。
(因为实际上400年就完成一轮循环,星期又重回原点了)


*/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值