# NENUOJ 之 算法1第6章
# 6001 第几天? (opens new window)
# 题目描述
给定一个日期,输出这个日期是该年的第几天。
# 输入
输入数据有多组,每组占一行,数据格式为YYYY/MM/DD组成,具体参见输入样例,另外,可以向你确保所有的输入数据是合法的。
# 输出
对于每组输入数据,输出一行,表示该日期是该年的第几天。
# 样例输入 复制
1985/1/20 2006/3/12
# 样例输出 复制
20 71
# 代码C语言 代码一:
#include<stdio.h>
#include<string.h>
#include<math.h>
#define debug printf("Hello")
int temp[]={31,28,31,30,31,30,31,31,30,31,30,31};
int main(){
int yy,mm,dd;
while(~scanf("%d/%d/%d",&yy,&mm,&dd)){
int flag = 0;
int i;
if((yy%4==0&&yy%100!=0)||yy%400==0){
flag = 1;
}
int sum = 0;
if(mm == 1){
sum += dd;
}
if(mm == 2){
sum += 31 + dd;
}
if(mm >= 3){
for(i = 0;i < mm-1;i++){
sum += temp[i];
printf("%d\t",temp[i]);
}
sum += dd;
if(flag == 1){
sum++;
}
}
printf("%d\n",sum);
}
return 0;
}
# 代码C语言 代码二:
#include<stdio.h>
int month[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
//判断是否是闰月
int judge(int year){
if(year % 400 == 0) return 1;
if(year % 100 != 0 && year % 4 == 0) return 1;
return 0;
}
int main(){
int y,m,d;
while(~scanf("%d/%d/%d",&y,&m,&d)){
int day = 0;
for(int i = 1;i < m;i++){
day += month[i];
}
day += d + judge(y);
printf("%d\n",day);
}
return 0;
}
# 6002 时间格式转换 (opens new window)
# 题目描述
世界各地有多种格式来表示日期和时间。对于日期的常用格式,在中国常采用格式的是“年年年年/月月/日日”或写为英语缩略表示的”yyyy/mm/dd”,此次编程大赛的启动日期“2009/11/07”就是符合这种格式的一个日期,而北美所用的日期格式则为“月月/日日/年年年年”或”mm/dd/yyyy”,如将“2009/11/07”改成这种格式,对应的则是”11/07/2009”。对于时间的格式,则常有12小时制和24小时制的表示方法,24小时制用0-23来表示一天中的24小时,而12小时制只采用1-12表示小时,再加上am/pm来表示上午或下午,比如”17:30:00”是采用24小时制来表示时间,而对应的12小时制的表示方法是”05:30:00pm”。注意12:00:00pm表示中午12点,而12:00:00am表示凌晨12点。 对于给定的采用”yyyy/mm/dd”加24小时制(用短横线”-”连接)来表示日期和时间的字符串,请编程实现将其转换成”mm/dd/yyyy”加12小时制格式的字符串。
# 输入
第一行为一个整数T(T <= 10),代表总共需要转换的时间日期字符串的数目。 接下来的总共T行,每行都是一个需要转换的时间日期字符串。
# 输出
分行输出转换之后的结果。
# 样例输入 复制
2 2009/11/07-12:12:12 1970/01/01-00:01:01
# 样例输出 复制
11/07/2009-12:12:12pm 01/01/1970-12:01:01am
# 代码C语言:
#include<stdio.h>
int main(){
int t;
scanf("%d",&t);
int yy,mm,dd;
int h,m,s;
while(t--){
scanf("%d/%d/%d-%d:%d:%d",&yy,&mm,&dd,&h,&m,&s);
if(h > 12) {
h -= 12;
printf("%02d/%02d/%04d-%02d:%02d:%02dpm\n",mm,dd,yy,h,m,s);
}
else if(h == 12) {
printf("%02d/%02d/%04d-%02d:%02d:%02dpm\n",mm,dd,yy,h,m,s);
}
else if(h < 12){
if(h == 0){
h = 12;
}
printf("%02d/%02d/%04d-%02d:%02d:%02dam\n",mm,dd,yy,h,m,s);
}
}
return 0;
}
# 6003 星期几? (opens new window)
# 题目描述
今天是2007年11月17日,星期六。现在如果我告诉你一个日期,你能告诉它是星期几吗?
# 输入
输入有多组测试数据,每个测试数据占一行。每行有3个整数,分别表示:年(0< year < 10000), 月(0 <= month < 13), 日(0 <= day < 32)。
# 输出
每个测试数据输出一行,如果输入数据不对,输出“illegal”,否则输出它是星期几。
# 样例输入 复制
2007 11 17
# 样例输出 复制
Saturday
# 思路:
这道题主要是算出来要求的那一天和2007.11.17差了多少天【既然是算差值,最后就要减1】然后把总和天数除以7的周期,余数就是0~6之间的一个,所对应的就是周一到周六的英文【提前存储好】
# 代码C语言:
#include<stdio.h>
int m[] = {31,28,31,30,31,30,31,31,30,31,30,31};
char str[10][10] = {"Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"};
int isrun(int yy){
if(yy % 400 == 0||(yy % 4 == 0 && yy % 100 != 0)){
return 1;
}else{
return 0;
}
}
int main(){
int yy,mm,dd;
while(~scanf("%d %d %d",&yy,&mm,&dd)){
int sum = 0;
if(yy <= 0||yy >= 10000||mm <= 0||mm >= 13||dd <= 0||dd >=32){
printf("illegal\n");
continue;
}
int i;
sum += 365 * (yy - 1);
for(i = 1;i < yy;i++){
sum += isrun(i);
}
for(i = 0;i < mm - 1;i++){
sum += m[i];
}
if(mm > 2){
sum += isrun(yy);
}
sum += dd - 1;
printf("%s\n",str[sum % 7]);
}
return 0;
}
# 6004 18岁生日 (opens new window)
# 题目描述
Gardon的18岁生日就要到了,他当然很开心,可是他突然想到一个问题,是不是每个人从出生开始,到达18岁生日时所经过的天数都是一样的呢?似乎并不全都是这样,所以他想请你帮忙计算一下他和他的几个朋友从出生到达18岁生日所经过的总天数,让他好来比较一下。
# 输入
一个整数T,表示测试数据的组数,接下来有T行日期,每行一个,格式是YYYY-MM-DD。如我的生日是1988-03-07。
# 输出
T行,每行一个数,表示此人从出生到18岁生日所经过的天数。如果这个人没有18岁生日,就输出-1。
# 样例输入 复制
1 1988-03-07
# 样例输出 复制
6574
# 代码C语言:
#include<stdio.h>
int m[] = {31,28,31,30,31,30,31,31,30,31,30,31};
int isrun(int yy){
if(yy % 400 == 0||(yy % 4 == 0 && yy % 100 != 0)){
return 1;
}
return 0;
}
int main(){
int t;
int yy,mm,dd;
scanf("%d",&t);
while(t--){
int sum = 0;
scanf("%d-%d-%d",&yy,&mm,&dd);
int i;
sum += 365 * 18;
//算上闰年
for(i = yy + 1;i < yy + 18;i++){
sum += isrun(i);
}
//出生是1、2月以外的,需要判断18岁那年是不是闰年
if(mm >= 3){
sum += isrun(yy + 18);
}
//出生是1、2月的,需要判断出生那年是不是闰年
if(mm <= 2){
sum += isrun(yy);
}
//闰年出生的人没有18岁生日
if(mm == 2 && dd == 29 && isrun(yy+18) == 0){
sum = -1;
}
printf("%d\n",sum);
}
return 0;
}
# 6005 游船出租 (opens new window)
# 题目描述
现有公园游船租赁处请你编写一个租船管理系统。当游客租船时,管理员输入船号并按下S键,系统开始计时;当游客还船时,管理员输入船号并按下E键,系统结束计时。船号为不超过100的正整数。当管理员将0作为船号输入时,表示一天租船工作结束,系统应输出当天的游客租船次数和平均租船时间。 注意:由于线路偶尔会有故障,可能出现不完整的纪录,即只有租船没有还船,或者只有还船没有租船的纪录,系统应能自动忽略这种无效纪录。
# 输入
测试输入包含若干测试数据,每个测试数据为一整天的租船纪录,格式为: 船号(1~100) 键值(S或E) 发生时间(小时:分钟) 每一天的纪录保证按时间递增的顺序给出。当读到船号为-1时,全部输入结束,相应的结果不要输出。
# 输出
对每个测试数据输出1行,即当天的游客租船次数和平均租船时间(以分钟为单位的精确到个位的整数时间)。
# 样例输入 复制
1 S 08:10 2 S 08:35 1 E 10:00 2 E 13:16 0 S 17:00 0 S 17:00 3 E 08:10 1 S 08:20 2 S 09:00 1 E 09:20 0 E 17:00 -1
# 样例输出 复制
2 196 0 0 1 60
# 思路:
这道题的思路就是,每个船有个开始和结束,所以把每艘船的开始做个标记,没有开始标记的一律不存在。如果有了开始标记,就在结束的时候把时间差加上,然后更新开始标记【顺便计数】。这道题一定一定要开double处理【参考下方代码】,原因笔者没有探索出来,原本以为是四舍五入的问题,但是最后还是报错。【最好笑的是把全网的题解复制过来都是错的hhh】
# 代码C语言:
#include <stdio.h>
int main() {
int start[101] = {0},boat,h,m;
char event[101] = {'\0'};
double count = 0,total = 0;
for (int i = 0; i < 101; i++) {
start[i] = -1;
}
while (scanf("%d %s %d:%d",&boat,event,&h,&m) != EOF) {
if (boat == -1) {
break;
}
//更改时间参照物,把时间转换成对应的分钟数
int minutes = h * 60 + m;
//如果按下0,开始输出
if(boat == 0){
if(count == 0){
printf("0 0\n");
}else{
printf("%.0f %.0f\n",count,total / count);
}
count = 0;
total = 0;
for(int i = 0;i < 101;i++){
start[i] = -1;
event[i] = '\0';
}
}else if(event[0] == 'S'){
start[boat] = minutes;
}else if(event[0] == 'E' && start[boat] != -1){
total += minutes - start[boat];
count++;
start[boat] = -1;
}
}
return 0;
}
# 6201日历问题 (opens new window)
# 题目描述
在我们现在使用的日历中, 闰年被定义为能被4 整除的年份,但是能被100整除而不能被400整除的年是例外,它们不是闰年。例如:1700,1800,1900和2100不是闰年,而1600, 2000 和2400是闰年。给定从公元2000年1月1日开始逝去的天数,你的任务是给出这一天是哪年哪月哪日星期几。
# 输入
输入包含若干行,每行包含一个正整数,表示从2000年1月1日开始逝去的天数。输入最后一行是−1, 不必处理。可以假设结果的年份不会超过9999。
# 输出
对每个测试样例,输出一行,该行包含对应的日期和星期几。格式为“YYYY-MM-DD DayOfWeek”, 其中 “DayOfWeek” 必须是下面中的一个: “Sunday”, “Monday”, “Tuesday”, “Wednesday”,“Thursday”,“Friday”和 “Saturday”。
# 样例输入 复制
1730 1740 1750 1751 -1
# 样例输出 复制
2004-09-26 Sunday 2004-10-06 Wednesday 2004-10-16 Saturday 2004-10-17 Sunday
# 代码C语言:
#include<stdio.h>
int m[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
char week[15][15] = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};
int isrun(int year){
if((year % 4==0 && year % 100 != 0) || year % 400 == 0) return 1;
return 0;
}
int main(){
int n;
while(~scanf("%d",&n)){
int j = n;
int yy = 2000,mm = 1,dd = 1;
int i = 1;
if(n == -1) break;
//直接模拟计算
while(n){
if(n >= 365 && !isrun(yy)){
yy++;
n -= 365;
}
else if(n >= 366 && isrun(yy)){
yy++;
n -= 366;
}
else {
while(n > m[i]){
if(isrun(yy) && i == 3) n -= 1;
n -= m[i];
mm++;
i++;
}
dd += n;
n -= n;
}
}
printf("%04d-%02d-%02d %s\n",yy,mm,dd,week[(j-1) % 7]);
}
return 0;
}
# 6202特殊日历计算 (opens new window)
# 题目描述
有一种特殊的日历法,它的一天和我们现在用的日历法的一天是一样长的。它每天有10个小时,每个小时有100分钟,每分钟有100秒。10天算一周,10周算一个月,10个月算一年。现在要你编写一个程序,将我们常用的日历法的日期转换成这种特殊的日历表示法。这种日历法的时、分、秒是从0开始计数的。日、月从1开始计数,年从0开始计数。秒数为整数。假设 0:0:0 1.1.2000 等同于特殊日历法的 0:0:0 1.1.0。
# 输入
第一行是一个正整数N,表明下面有N组输入。每组输入有一行,格式如下:hour:minute:second day.month.year 表示常规的日期。日期总是合法的。2000 <= year <= 50000。
# 输出
每组输入要求输出一行。格式如下:mhour:mmin:msec mday.mmonth.myear 是输入日期的特殊日历表示方法。
# 样例输入 复制
7 0:0:0 1.1.2000 10:10:10 1.3.2001 0:12:13 1.3.2400 23:59:59 31.12.2001 0:0:1 20.7.7478 0:20:20 21.7.7478 15:54:44 2.10.20749
# 样例输出 复制
0:0:0 1.1.0 4:23:72 26.5.0 0:8:48 58.2.146 9:99:98 31.8.0 0:0:1 100.10.2000 0:14:12 1.1.2001 6:63:0 7.3.6848
# 思路:
其实时间就是一种进制,通常时间的进制是60进制,而现在要求我们转换成十进制。那我们的做法就是先把时间都转换成秒钟,然后再把秒钟用十进制处理,就能得到秒,分钟,小时。日期也是同理,转换成最低的标准(分钟)后,再慢慢转换成天数,月份,年(天数除以1000)。
# 代码C语言:
#include<stdio.h>
int months[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
int isrun(int year){
if((year % 4==0 && year % 100 != 0) || year % 400 == 0) return 1;
return 0;
}
int main(){
int n;
scanf("%d",&n);
while(n--){
int i;
int hh,mm,ss,d,m,y;
int sum = 0;
long long tot = 0;
scanf("%d:%d:%d %d.%d.%d",&hh,&mm,&ss,&d,&m,&y);
//得到秒数(已经是新单位了)
tot = 100000LL * (hh * 3600 + mm * 60 + ss)/(3600 * 24);
//得到天数
for(i = 2000;i < y;i++){
if(isrun(i)) sum += 366;
else sum += 365;
}
for(i = 1;i < m;i++){
sum += months[i];
if(i == 3 && isrun(y)) sum += 1;
}
if(m == 3 && isrun(y)) sum += 1;
sum += d;
sum -= 1;
//进制转换输出
printf("%d:%d:%d %d.%d.%d\n",tot/10000,tot%10000/100,tot%100,sum%100+1,sum%1000/100+1,sum/1000);
}
return 0;
}
# 6203 时区转换 (opens new window)
# 题目描述
直到19世纪,时间校准是一个纯粹的地方现象。每一个村庄当太阳升到最高点的时候把他们的时钟调到中午12点。一个钟表制造商人家或者村里主表的时间被认为是官方时间,市民们把自家的钟表和这个时间对齐。每周一些热心的市民会带着时间标准的表,游走大街小巷为其他市民对表。在城市之间旅游的话,在到达新地方的时候需要把怀表校准。但是,当铁路投入使用之后,越来越多的人频繁地长距离地往来,时间变得越来越重要。在铁路的早期,时刻表非常让人迷惑,每一个所谓的停靠时间都是基于停靠地点的当地时间。时间的标准化对于铁路的高效运营变得非常重要。 在1878年,加拿大人Sir Sanford Fleming提议使用一个全球的时区(这个建议被采纳,并衍生了今天我们所使用的全球时区的概念),他建议把世界分成24个时区,每一个跨越15度经线(因为地球的经度360度,划分成24块后,一块为15度)。Sir Sanford Fleming的方法解决了一个全球性的时间混乱的问题。 美国铁路公司于1883年11月18日使用了Fleming提议的时间方式。1884年一个国际子午线会议在华盛顿召开,他的目的是选择一个合适的本初子午线。大会最终选定了格林威治为标准的0度。尽管时区被确定了下来,但是各个国家并没有立刻更改他们的时间规范,在美国,尽管到1895年已经有很多州开始使用标准时区时间,国会直到1918年才强制使用会议制定的时间规范。 今天各个国家使用的是一个Fleming时区规范的一个变种,中国一共跨越了5个时区,但是使用了一个统一的时间规范,比Coordinated Universal Time(UTC,格林威制时间)早8个小时。俄罗斯也拥护这个时区规范,尽管整个国家使用的时间和标准时区提前了1个小时。澳大利亚使用3个时区,其中主时区提前于他按Fleming规范的时区半小时。很多中东国家也使用了半时时区(即不是按照Fleming的24个整数时区)。 因为时区是对经度进行划分,在南极或者北极工作的科学家直接使用了UTC时间,否则南极大陆将被分解成24个时区。 时区的转化表如下: UTC(Coordinated Universal Time) GMT(Greenwich Mean Time),定义为UTC小时 BST(British Summer Time),定义为 UTC+1小时 IST(Irish Summer Time),定义为 UTC+1小时 WET(Western Europe Time),定义为 UTC小时 WEST(Western Europe Summer Time),定义为 UTC+1小时 CET(Central Europe Time),定义为 UTC+1小时 CEST(Central Europe Summer Time),定义为 UTC+2小时 EET(Eastern Europe Time),定义为 UTC+2小时 EEST(Eastern Europe Summer Time),定义为 UTC+3小时 MSK(Moscow Time),定义为 UTC+3小时 MSD(Moscow Summer Time),定义为 UTC+4小时 AST(Atlantic Standard Time),定义为 UTC-4小时 ADT(Atlantic Daylight Time),定义为 UTC-3小时 NST(Newfoundland Standard Time),定义为 UTC-3.5小时 NDT(Newfoundland Daylight Time),定义为 UTC-2.5小时 EST(Eastern Standard Time),定义为 UTC-5小时 EDT(Eastern Daylight Saving Time),定义为 UTC-4小时 CST(Central Standard Time),定义为 UTC-6小时 CDT(Central Daylight Saving Time),定义为 UTC-5小时 MST(Mountain Standard Time),定义为 UTC-7小时 MDT(Mountain Daylight Saving Time),定义为 UTC-6小时 PST(Pacific Standard Time),定义为 UTC-8小时 PDT(Pacific Daylight Saving Time),定义为 UTC-7小时 HST(Hawaiian Standard Time),定义为 UTC-10小时 AKST(Alaska Standard Time),定义为 UTC-9小时 AKDT(Alaska Standard Daylight Saving Time),定义为 UTC-8小时 AEST(Australian Eastern Standard Time),定义为 UTC+10小时 AEDT(Australian Eastern Daylight Time),定义为 UTC+11小时 ACST(Australian Central Standard Time),定义为 UTC+9.5小时 ACDT(Australian Central Daylight Time), 定义为 UTC+10.5小时 AWST(Australian Western Standard Time), 定义为 UTC+8小时 下面给出了一些时间,请在不同时区之间进行转化。
# 输入
输入的第一行包含了一个整数N,表示有N组测试数据。接下来N行,每一行包括一个时间和两个时区的缩写,它们之间用空格隔开。时间由标准的a.m./p.m 给出。midnight表示晚上12点(12:00 a.m.),noon表示中午12点(12:00 p.m.)。
# 输出
假设输入行给出的时间是在第一个时区中的标准时间,要求输出这个时间在第二个时区中的标准时间。
# 样例输入 复制
4 noon HST CEST 11:29 a.m. EST GMT 6:01 p.m. CST UTC 12:40 p.m. ADT MSK
# 样例输出 复制
midnight 4:29 p.m. 12:01 a.m. 6:40 p.m.
# 思路:
- 解析时间:将给定的时间(可能是 noon 和 midnight 或 标准时间格式)转换为24小时制的时间。
- 查找时区偏移量:根据输入和输出时区找到它们相对于UTC的偏移量。
- 计算新时间:使用时区偏移量来计算目标时区中的时间。
- 格式化输出:将计算得到的时间转换回a.m./p.m.格式,包括处理特殊的 noon 和 midnight 。
# 代码C语言:
#include <stdio.h>
#include <string.h>
// 时区偏移量(以分钟为单位)
int getTimeZoneOffset(char timeZone[]) {
if (strcmp(timeZone, "UTC") == 0) return 0;
if (strcmp(timeZone, "GMT") == 0) return 0;
if (strcmp(timeZone, "BST") == 0 || strcmp(timeZone, "IST") == 0 || strcmp(timeZone, "WEST") == 0) return 60;
if (strcmp(timeZone, "WET") == 0) return 0;
if (strcmp(timeZone, "CET") == 0) return 60;
if (strcmp(timeZone, "CEST") == 0) return 120;
if (strcmp(timeZone, "EET") == 0) return 120;
if (strcmp(timeZone, "EEST") == 0) return 180;
if (strcmp(timeZone, "MSK") == 0) return 180;
if (strcmp(timeZone, "MSD") == 0) return 240;
if (strcmp(timeZone, "AST") == 0) return -240;
if (strcmp(timeZone, "ADT") == 0) return -180;
if (strcmp(timeZone, "NST") == 0) return -210;
if (strcmp(timeZone, "NDT") == 0) return -150;
if (strcmp(timeZone, "EST") == 0) return -300;
if (strcmp(timeZone, "EDT") == 0) return -240;
if (strcmp(timeZone, "CST") == 0) return -360;
if (strcmp(timeZone, "CDT") == 0) return -300;
if (strcmp(timeZone, "MST") == 0) return -420;
if (strcmp(timeZone, "MDT") == 0) return -360;
if (strcmp(timeZone, "PST") == 0) return -480;
if (strcmp(timeZone, "PDT") == 0) return -420;
if (strcmp(timeZone, "HST") == 0) return -600;
if (strcmp(timeZone, "AKST") == 0) return -540;
if (strcmp(timeZone, "AKDT") == 0) return -480;
if (strcmp(timeZone, "AEST") == 0) return 600;
if (strcmp(timeZone, "AEDT") == 0) return 660;
if (strcmp(timeZone, "ACST") == 0) return 570;
if (strcmp(timeZone, "ACDT") == 0) return 630;
if (strcmp(timeZone, "AWST") == 0) return 480;
return 0;
}
//将12小时制时间转换为分钟
int convertTimeToMinutes(char time[],char meridiem) {
int hours,minutes;
if (strcmp(time,"noon") == 0) return 12 * 60;
if (strcmp(time,"midnight") == 0) return 0;
sscanf(time,"%d:%d",&hours,&minutes);
if (meridiem == 'p' && hours != 12) hours += 12;
if (meridiem == 'a' && hours == 12) hours = 0;
return hours * 60 + minutes;
}
//将分钟转换为12小时制时间
void convertMinutesToTime(int minutes, char time[]) {
int hours = (minutes / 60) % 24;
int mins = minutes % 60;
char meridiem[] = "a.m.";
if (hours >= 12) {
strcpy(meridiem,"p.m.");
if (hours > 12) hours -= 12;
}
if (hours == 0) hours = 12;
if (hours == 12 && mins == 0 && strcmp(meridiem,"p.m.") == 0) strcpy(time, "noon");
else if (hours == 12 && mins == 0 && strcmp(meridiem,"a.m.") == 0) strcpy(time, "midnight");
else sprintf(time,"%d:%02d %s",hours,mins,meridiem);
}
int main() {
int n;
scanf("%d", &n);
char inputTime[10],fromTimeZone[10],toTimeZone[10],meridiem[5],outputTime[20];
for (int i = 0; i < n; i++) {
scanf("%s",inputTime);
if (strcmp(inputTime,"noon") == 0 || strcmp(inputTime,"midnight") == 0) {
strcpy(meridiem,"a.m.");
} else {
scanf("%s",meridiem); //读取a.m.或p.m.
}
scanf("%s %s",fromTimeZone,toTimeZone);
//求出分钟数
int fromOffset = getTimeZoneOffset(fromTimeZone);
int toOffset = getTimeZoneOffset(toTimeZone);
//计算时间差
int timeDifference = toOffset - fromOffset;
//输入的标准时间
int inputMinutes = convertTimeToMinutes(inputTime,meridiem[0]);
//输出的标准时间
int outputMinutes = (inputMinutes + timeDifference + 1440) % 1440;
convertMinutesToTime(outputMinutes,outputTime);
printf("%s\n",outputTime);
}
return 0;
}
# 6204 幸运月 (opens new window)
# 题目描述
你是不是很奇怪,一年为啥365天而不是400天?为啥8月份是31天,但是2月份却只有28天?为啥一个星期是7天而不是6天?古时候的人们也是使用和我们现在一样的历法吗?关于这些问题有很多有趣的猜测了。现在,我们将告诉你其中的一个故事以便帮助你解释这些问题。借助于故事里的信息,你可以使用计算机来解决一个有趣的问题。关于历法有很多理论,设置这个问题只是以简单的方式告诉你其中的一个理论。 纵观历史,人们通过观察地球、月亮和太阳间的相对位置来记录时间。地球自转一圈的时间就是一天。地球围绕太阳转一圈的时间记为一年,实际上需要365.242190 天。为了实用,一个历法年需要一个整数天数。因此人们需要增加闰天,以保持历法和太阳同步。如果你保持一年365天,那么你需要在差不多每4年一个闰年中增加1天左右。然而,这种历法并不能和地球围绕太阳旋转完全保持一致,因为平均下来每年是365.25天,这个要比实际的要多一点点时间。 这取决于你如何准确测量地球绕太阳公转的周期,你需要创造不同的闰年公式。一些西方著名的历法系统已经被发明,除了更复杂的东方历法系统。为了节省程序员的努力,我们将不讨论东方历法系统,如中国的农历。我们将专注于西方主要的历法系统。最早可能是凯撒在公元前46年建立的儒略历。它不够准确,每128年将休息一天。下一个是天文儒略历,由约瑟夫•尤斯图斯凯利格在16世纪前后发明的。这两个历法都有个简单的公式用来确定哪一年是闰年。 下一个主要历法系统是在1582年发明的被称为公历,因为人们终于注意到了地球的轨道和日历同步。在这个系统中,闰年每100年被丢弃一次,除非正好是400年。通过做这个修改,在一个日历年度的平均天数为365.2425。注意,该系统还不够完善,每3289年还会多一天。还有其他更多的修改建议,如一个由天文学家约翰•赫歇尔,希腊东正教,并在美国海军SPAWAR组。为了简便起见,人们还是采用了公历制,虽然它可能不是完美的。 以下是公历的计算闰年公式。对于y年,y>1582且y≠1700,当且仅当是满足下面的条件是y就是闰年: •y能被4整除,而且 •y不能被100整除除非它能被400整除 若0 < y < 1582,当且仅当满足下面的条件它才是闰年: •y能被4整除 因此,4年是闰年,100年是闰年,1900年不是闰年,但是2000是闰年。闰年有366天,额外的那天就是二月29日。非闰年有365天。 在你的计算中,你可能还需要观察有关公历的以下事实。在西方世界里,许多日历系统,被同时用在不同的领域里。目前西方的历法系统,主要遵循了格利高里历法,英国在1752年9月3日通过了所谓的公历改革。由于众多原因,我们相信你不想阅读这个问题的描述。为了不重写历史,人们决定消除从1752年9月3日开始的11天。也就是说,在公历中,在1752年9月3日到1752年9月13日之间的11天是没有的。需要注意的是罗马在1582年开始采用公历。此外,由于历史原因,1700年被宣布为公历闰年。关于公历还有其他的变种,但是,我们将使用上述的定义。不同的有关公历制有其它的变化,但是,我们将使用如上所定义的一方。 阴历一个月被定义为连续的新的或满月之间的平均时间是29.531天。人们观察到在一年平均12.368满月。不幸的是,这不是整数个天。因此,如果我们一年有12个月,每月30天,我们每年需要新增几天。为了解决这个麻烦,一种替换的方法就是一个月的天数介于30和31之间。然而,这就会引入一个额外的一天。经过多次争论,公历最终给出在非闰年中每个月的天数分别是31,28,31,30,31,30,31,31,30,31,30,31,分别从1月到12月。闰年中多的那一天添加到2月份中。各个月份的名称分别为:January, February, March, April, May, June, July, August, September, October, November和December。这些名称也很有意思,有很多与它们相关的故事。例如,罗马元老院任命的7月为July,以表彰凯撒为改革他们的日历而做出的贡献。然而,我们也没有时间在这里谈论它们。 在古代,一个星期可能有不同的天数,例如从4到10天。在公历系统中,一个星期大致相当于月球的季相,即人们可以清楚地观察到其位置的时间。因此,人们可以很容易地测量一个星期。此外,由于一些其他原因,例如宗教,最终被定义为7天。这7天的名字分别为:Sunday, Monday, Tuesday, Wednesday, Thursday, Friday 和 Saturday。也都有着有趣的故事。然而,我们也没有时间在这里谈论它们。 纵观历史,人们相信星星的相对位置可以决定自己的命运。这也是个真实的故事,一些人住在一个叫T的小岛上。在T岛,人们每周从周一工作至周五,周六、周日享受两天的假期,除此之外没有其他假期。有个古老的传说,有这样一个幸运月,如果在这个月的最后一个工作日恰好是星期五的话,那么这个月就被称为幸运的。举例来说,2006年9月份的最后一个工作日是9月29日,这天正好是星期五。因此,它是幸运的。2006年7月的最后一个工作日是2006年7月31日,这天是星期一,因此它不是幸运的。2006年8月的最后一个工作日是8月31日,这天是星期四。因此,它也不是幸运的。据说,如果一个人在幸运月里每天只吃蔬菜的话,他/她将拥有财富。 此外,根据另外的传说,如果一个月的第一个工作日恰好是星期一的话,那么这个月就被称为一个好月。举例来说,2006年7月的第一个工作日为7月3日,这是周一。因此,它是好月。 2006年10月的第一个工作日是10月2,这是周一。因此,它也是好月。 2006年8月的第一个工作日为2006年8月1日,这是周二。因此,它不是好月的。2006年9月的第一个工作日是9月1日,这是星期五。因此,它也不是好月。据说,如果一个人在好月的每天晚上10点前上床睡觉的话,他/她将是非常健康的。一个月可能同时为好月和幸运月。 给定一个时间段,你的任务就是给出在这个时间段内好月和幸运月的数量。
# 输入
第一行是测试数据的组数w,1 ≤ w ≤ 10. 然后是w组测试数据依次罗列出来。每个测试数据包括1行,有4个数字构成: Ys Ms Ye Me 两两之间空格隔开,Ys是一个整数, 0 < Ys < 10000,这是起始的年份,Ms也是个整数, 1 ≤ Ms ≤ 12,表示起始月份。Ye 是一个整数, 0 < Ye < 10000,表示结束的年份, Me 是整数,1 ≤ Me ≤ 12,表示结束的月份。 注意:Ms,Ys表示的月份不会在Me,Ye表示的月份之后。
# 输出
对于每个测试数据,在一行输出在Ys年Ms月(包括Ms月)到Ye年Me月(包括Me月)之间的幸运月的数量和好月的数量,以空格隔开。
# 样例输入 复制
2 2006 9 2006 9 2006 7 2006 9
# 样例输出 复制
1 0 1 1
# 思路:
这道题首先有个陷阱是,它要一直读入到文件尾【不止一组数据】
然后就是按题目模拟,大概思路就是:
- 判断闰年
- 判断每个月最后一天是哪一天
- 判断某年某月某天是星期几【重点】
- 从第1天往后找好月
- 从最后1天往前找幸运月
怎么判断某年某月某天是星期几?两种办法,一个是公式法,另一个就是暴力。
公式法就是用Zeller公式直接求,暴力就是,比如我们知道1年1月1日是星期六,在这个基础上统计总天数【从当前年月日到1年1月1日之间的总天数】,然后就可以求
根本没必要模拟题目说的那些要求,因为数据里根本没有那些复杂的。连闰年都只需要用$\text{year} \mod 4 = 0 \quad \text{AND} \quad \text{year} \mod 100 \neq 0 \quad \text{OR} \quad \text{year} \mod 400 = 0$判断就够了。
下方代码一就是用了各种公式+完成题目要求做的
下方代码二是用了Zeller公式做的
下方代码三是用了暴力做的
# 代码C语言 代码一:
#include <stdio.h>
//判断是否为闰年
int isLeapYear(int year) {
if(year == 1700){
return 1;
}
if(year > 1582 && year != 1700)
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
if(year < 1582){
return year % 4 == 0;
}
return 0;
}
//获取一个月的最后一天
int daysInMonth(int year, int month) {
int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (month == 2 && isLeapYear(year)) {
return 29;
}
return days[month - 1];
}
//采用公式
int dayOfWeek(int y, int m, int d) {
if (m == 1 || m == 2) {
m += 12;
y--;
}
if ((y > 1752) || (y == 1752 && (m > 9 || (m == 9 && d >= 14)))) {
// 1752年9月14日及之后的日期
return (d + 2 * m + 3 * (m + 1) / 5 + y + y / 4 - y / 100 + y / 400) % 7 + 1;
} else if ((y < 1582) || (y == 1582 && (m < 10 || (m == 10 && d < 5)))) {
// 1582年10月4日之前的日期
return (d + 2 * m + 3 * (m + 1) / 5 + y + y / 4 + 5) % 7 + 1;
} else {
// 1582年10月4日至1752年9月3日的日期
return (d + 2 * m + 3 * (m + 1) / 5 + y + y / 4 - y / 100 + y / 400) % 7 + 1;
}
}
// 查找一个月中最后一个工作日
int findLastWeekday(int year, int month) {
int day = daysInMonth(year,month);
//如果是星期六或星期天
while (dayOfWeek(year, month, day) == 6 || dayOfWeek(year,month,day) == 7){
day--;
}
return day;
}
// 查找一个月中第一个工作日
int findFirstWeekday(int year, int month) {
int day = 1;
//如果是星期六或星期天
while (dayOfWeek(year,month,day) == 6 || dayOfWeek(year,month,day) == 7){
day++;
}
return day;
}
int main() {
int w;
while(~scanf("%d", &w)){
while (w--) {
int ys, ms, ye, me;
scanf("%d %d %d %d",&ys,&ms,&ye,&me);
int luckyMonths = 0,goodMonths = 0;
for (int year = ys;year <= ye;year++) {
int startMonth = (year == ys) ? ms : 1;//起始月特判
int endMonth = (year == ye) ? me : 12;//终止月特判
for (int month = startMonth;month <= endMonth;month++) {
//如果最后一个工作日是星期五
if (dayOfWeek(year,month,findLastWeekday(year, month)) == 5) {
luckyMonths++;//幸运月
}
//如果第一个工作日是星期一
if (dayOfWeek(year,month,findFirstWeekday(year, month)) == 1) {
goodMonths++;//好月
}
}
}
printf("%d %d\n",luckyMonths,goodMonths);
}
}
return 0;
}
# 代码C语言 代码二:
#include <stdio.h>
//判断是否为闰年
int isLeapYear(int year) {
if(year == 1700){
return 1;
}
if(year > 1582 && year != 1700)
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
if(year < 1582){
return year % 4 == 0;
}
return 0;
}
//获取一个月的最后一天
int daysInMonth(int year, int month) {
int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (month == 2 && isLeapYear(year)) {
return 29;
}
return days[month - 1];
}
//公式法
int dayOfWeek(int y, int m, int d) {
if (m == 1 || m == 2) {
m += 12;
y--;
}
int h = (d + 2 * m + 3 * (m + 1) / 5 + y + y / 4 - y / 100 + y / 400) % 7;
return h + 1;
}
// 查找一个月中最后一个工作日
int findLastWeekday(int year, int month) {
int day = daysInMonth(year,month);
//如果是星期六或星期天
while (dayOfWeek(year, month, day) == 6 || dayOfWeek(year,month,day) == 7){
day--;
}
return day;
}
// 查找一个月中第一个工作日
int findFirstWeekday(int year, int month) {
int day = 1;
//如果是星期六或星期天
while (dayOfWeek(year,month,day) == 6 || dayOfWeek(year,month,day) == 7){
day++;
}
return day;
}
int main() {
int w;
while(~scanf("%d", &w)){
while (w--) {
int ys, ms, ye, me;
scanf("%d %d %d %d",&ys,&ms,&ye,&me);
int luckyMonths = 0,goodMonths = 0;
for (int year = ys;year <= ye;year++) {
int startMonth = (year == ys) ? ms : 1;//起始月特判
int endMonth = (year == ye) ? me : 12;//终止月特判
for (int month = startMonth;month <= endMonth;month++) {
//如果最后一个工作日是星期五
if (dayOfWeek(year,month,findLastWeekday(year, month)) == 5) {
luckyMonths++;//幸运月
}
//如果第一个工作日是星期一
if (dayOfWeek(year,month,findFirstWeekday(year, month)) == 1) {
goodMonths++;//好月
}
}
}
printf("%d %d\n",luckyMonths,goodMonths);
}
}
return 0;
}
# 代码C语言 代码三:
#include <stdio.h>
//判断是否为闰年
int isLeapYear(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
//获取一个月的最后一天
int daysInMonth(int year, int month) {
int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (month == 2 && isLeapYear(year)) {
return 29;
}
return days[month - 1];
}
//暴力求星期几
int dayOfWeek(int year,int month,int day){
int sumDay = 0;
//计算年转化成天
for (int i = 1;i < year;i++)
{
//是否是闰年
if((i % 4 == 0 && i % 100 != 0) || i % 400 == 0){
sumDay += 366;
}else{
sumDay += 365;
}
}
//计算月转化成天
for(int j = 1;j < month;j++){
if(j == 1 || j == 3 || j == 5 || j == 7 || j == 8 || j == 10 ){
sumDay += 31;
}else if( j == 2 ){
if((year % 4 == 0 && year % 100 != 0) || year % 400 == 0){
sumDay += 29;
}else{
sumDay += 28;
}
}else{
sumDay += 30;
}
}
//计算天
sumDay+= day ;
return sumDay % 7;
}
//查找一个月中最后一个工作日
int findLastWeekday(int year, int month) {
int day = daysInMonth(year, month);
//如果是星期六或星期天
while (dayOfWeek(year, month, day) == 6 || dayOfWeek(year, month, day) == 0) {
day--;
}
return day;
}
//查找一个月中第一个工作日
int findFirstWeekday(int year, int month) {
int day = 1;
//如果是星期六或星期天
while (dayOfWeek(year, month, day) == 6 || dayOfWeek(year, month, day) == 0) {
day++;
}
return day;
}
int main() {
int w;
while(~scanf("%d", &w)){
while (w--) {
int ys, ms, ye, me;
scanf("%d %d %d %d",&ys,&ms,&ye,&me);
int luckyMonths = 0,goodMonths = 0;
for (int year = ys;year <= ye;year++) {
int startMonth = (year == ys) ? ms : 1;//起始月特判
int endMonth = (year == ye) ? me : 12;//终止月特判
for (int month = startMonth;month <= endMonth;month++) {
//如果最后一个工作日是星期五
if (dayOfWeek(year,month,findLastWeekday(year, month)) == 5) {
luckyMonths++;//幸运月
}
//如果第一个工作日是星期一
if (dayOfWeek(year,month,findFirstWeekday(year, month)) == 1) {
goodMonths++;//好月
}
}
}
printf("%d %d\n",luckyMonths,goodMonths);
}
}
return 0;
}
# 总结
- 题目总体难度不难,就是得慢慢模拟。
- 6005游船出租和6203 时区转换难度较大,要考虑的要素较多,读者要慢慢思考。
- 6204 幸运月要考虑多组样例,不用看那个长长的题目,对解题没有影响。
- 有个很重要的关于时间的思想就是,标准化。比如把一天的时间转换成分钟等等。