[每日一题] 62. 美国节日(日期计算、蔡勒公式)

1. 题目来源

链接:美国节日
来源:牛客网

2. 题目说明

和中国的节日不同,美国的节假日通常是选择某个月的第几个星期几这种形式,因此每一年的放假日期都不相同。具体规则如下:

  • 1月1日:元旦
  • 1月的第三个星期一:马丁·路德·金纪念日
  • 2月的第三个星期一:总统节
  • 5月的最后一个星期一:阵亡将士纪念日
  • 7月4日:美国国庆
  • 9月的第一个星期一:劳动节
  • 11月的第四个星期四:感恩节
  • 12月25日:圣诞节
    现在给出一个年份,请你帮忙生成当年节日的日期。

输入描述:

输入包含多组数据,每组数据包含一个正整数year(2000≤year≤9999)。

输出描述:

对应每一组数据,以“YYYY-MM-DD”格式输出当年所有的节日日期,每个日期占一行。
每组数据之后输出一个空行作为分隔。

示例:

输入
2014
2013

输出
2014-01-01
2014-01-20
2014-02-17
2014-05-26
2014-07-04
2014-09-01
2014-11-27
2014-12-25

2013-01-01
2013-01-21
2013-02-18
2013-05-27
2013-07-04
2013-09-02
2013-11-28
2013-12-25

3. 题目解析

题目表述很明白,难点在于我们要求一个月第N个星期W。那么面对这个问题,我们拆解的思路是,首先,我们要想找到一个月第N个星期W,一定需要一个参照物,最好的目标当然是这个月的第一天。拿到参照物后,我要能得到参照物的星期数,然后就能得到结果了。所以这个题有两个难点:判断某个月的1号到底是周几,然后根据这个星期数得到这个月第N个星期W。这两个功能写成函数,即可通过反复调用拿到结果。

第一个难点是如何判断一天到底是周几。那么为了判断一天到底是周几,我们有以下两种手段:
1、找个参照日,写一个日期计算器,算出两个天数之间的差值后对7取余数即可知道目标日期是星期几。
2、通过蔡勒公式计算星期数:
w = ( [ c 4 − 2 c + y + [ y 4 ] + [ 13 × ( m + 1 ) 5 + d − 1 ) M O D 7 w=([\frac{c}{4}-2c+y+[\frac{y}{4}]+[\frac{13\times{}(m+1)}{5}+d-1) {\rm MOD7} w=([4c2c+y+[4y]+[513×(m+1)+d1)MOD7
公式中w就是计算出的星期;c是世纪,其值为真实的世纪数-1,也就是y / 100的结果;y是这是这个世纪的第几年,也就是y % 100;m是月,但是1月和2月要看做13月和14月计算;d是日。根据泰勒公式,我们只需要知道年月日,就可以算出星期。然而因为现行的格里历,所以在1582年之前的日期,我们要采用另一个公式,本题中用不到,可以参考百度百科:蔡勒公式-百度百科
两个方法比较而言,肯定是方法2的代码更好写,所以我们就用蔡勒公式就好。
第一个难点解决了,第二个难点,如何通过这个参照点拿到一个月的第N个星期W。

那么,我们假设要拿到一个月的第一个周五,我们要怎么做呢?一个很简单的思路就是,先看看这个月的1号是周几,然后往后数就行了,假如1号是周四,那么2号就是第一个周五,假如1号是周六,那么7号就是第一个周五。

那么怎么拿到这个向后的天数呢?我们发现,如果所求星期数比1号星期数大,那么直接相减后+1就是那一天了,例如1号周三,我要周五,那么(5-3)+1即可求出第一个周五是3号。那么反过来是所求星期数小,例如1号周三,我要周一,那么显然要先把周一看成周八才行。也就是(8-3)+1。第一个周一是6号。但是这样要判断,所以干脆统统都让它加7以后减,减完后的结果再mod一下7,就能得到结果了。也就是:(所求星期数 + 7 - 1号星期数) % 7 + 1。这样我们就拿到了求第一个周几公式。随后,我们只需要在这个公式上,加上7 * (n - 1),即刻求出第n个周几。而面对某个月的最后一个周几,我们要做的是拿到下个月的第一天然后往回推即可。公式跟上面的差不多。

4. 代码展示

// write your code here cpp
#include <iostream>
#include <cstdio>

// 根据 年-月-日 通过蔡勒公式计算当前星期几
// 1: 星期一 ... 7: 星期日
int day_of_week(int year, int month, int day)
{
    if (month == 1 || month == 2)
    {
        month += 12;
        year -= 1;
    }
 
    int century = year / 100;
    year %= 100;
    int week = year + (year / 4) + (century / 4) - 2 * century + 26 * (month + 1) / 10 + day - 
1;
    week = (week % 7 + 7) % 7;
 
    if (week == 0)
    {
        week = 7;
    }
 
    return week;
}
 
int day_of_demand(int year, int month, int count, int d_of_week)
{
    int week = day_of_week(year, month, 1); //求出1号星期数
    // 1 + 7(n - 1) + (所求星期数 + 7 - 1号星期数) % 7
    int day = 1 + (count - 1) * 7 + (7 + d_of_week - week) % 7;
    return day;
}
 
// 元旦
void new_year_day(int year)
{
    printf("%d-01-01\n", year);
}
 
// 马丁·路德·金纪念日(1月的第三个星期一)
void martin_luther_king_day(int year)
{
    printf("%d-01-%02d\n", year, day_of_demand(year, 1, 3, 1));
}
 
// 总统日(2月的第三个星期一)
void president_day(int year)
{
    printf("%d-02-%02d\n", year, day_of_demand(year, 2, 3, 1));
}
 
// 阵亡将士纪念日(5月的最后一个星期一)
void memorial_day(int year)
{
    // 从 6 月往前数
    int week = day_of_week(year, 6, 1);
    // 星期一的话,从 31 号往前数 6 天,否则,数 week - 2 天
    int day = 31 - ((week == 1) ? 6 : (week - 2));
    printf("%d-05-%02d\n", year, day);
}
 
// 国庆
void independence_day(int year)
{
    printf("%d-07-04\n", year);
}
 
// 劳动节(9月的第一个星期一)
void labor_day(int year)
{
    printf("%d-09-%02d\n", year, day_of_demand(year, 9, 1, 1));
}
 
// 感恩节(11月的第四个星期四)
void thanks_giving_day(int year)
{
    printf("%d-11-%02d\n", year, day_of_demand(year, 11, 4, 4));
}
 
// 圣诞节
void christmas(int year)
{
    printf("%d-12-25\n", year);
}
 
// 美国节日
void holiday_of_usa(int year)
{
    new_year_day(year);
    martin_luther_king_day(year);
    president_day(year);
    memorial_day(year);
    independence_day(year);
    labor_day(year);
    thanks_giving_day(year);
    christmas(year);
}
 
int main()
{
    int year;
    while (std::cin >> year)
    {
        holiday_of_usa(year);
        putchar('\n');
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值