问题 B: 日历本
时间限制: 1 Sec 内存限制: 32 MB
题目描述
我们经常需要使用日历,所以需要一个能生成日历的程序。
先要求你写一个程序,只需要输入年份,就能生成正确的日历。输入
输入包含多组测试数据。每组输入一个整数Y(1800<=Y<=2100),表示公元年份。
输出
对于每组输入,输出对应的日立本。选中下面的输出样例查看具体的输出格式,注意空格的输出。
样例输入 Copy
2010
样例输出 Copy
(注:不包括行号)
2010 January February March Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa 1 2 1 2 3 4 5 6 1 2 3 4 5 6 3 4 5 6 7 8 9 7 8 9 10 11 12 13 7 8 9 10 11 12 13 10 11 12 13 14 15 16 14 15 16 17 18 19 20 14 15 16 17 18 19 20 17 18 19 20 21 22 23 21 22 23 24 25 26 27 21 22 23 24 25 26 27 24 25 26 27 28 29 30 28 28 29 30 31 31 April May June Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa 1 2 3 1 1 2 3 4 5 4 5 6 7 8 9 10 2 3 4 5 6 7 8 6 7 8 9 10 11 12 11 12 13 14 15 16 17 9 10 11 12 13 14 15 13 14 15 16 17 18 19 18 19 20 21 22 23 24 16 17 18 19 20 21 22 20 21 22 23 24 25 26 25 26 27 28 29 30 23 24 25 26 27 28 29 27 28 29 30 30 31 July August September Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa 1 2 3 1 2 3 4 5 6 7 1 2 3 4 4 5 6 7 8 9 10 8 9 10 11 12 13 14 5 6 7 8 9 10 11 11 12 13 14 15 16 17 15 16 17 18 19 20 21 12 13 14 15 16 17 18 18 19 20 21 22 23 24 22 23 24 25 26 27 28 19 20 21 22 23 24 25 25 26 27 28 29 30 31 29 30 31 26 27 28 29 30 October November December Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa 1 2 1 2 3 4 5 6 1 2 3 4 3 4 5 6 7 8 9 7 8 9 10 11 12 13 5 6 7 8 9 10 11 10 11 12 13 14 15 16 14 15 16 17 18 19 20 12 13 14 15 16 17 18 17 18 19 20 21 22 23 21 22 23 24 25 26 27 19 20 21 22 23 24 25 24 25 26 27 28 29 30 28 29 30 26 27 28 29 30 31
笔记
这就是个纯模拟的题,但看起来似乎很麻烦,且无从下手。
我们不妨从样例输出上入手,可发现大概可以将一张日历划分为title和四个line,每个line包含三个block:
而每个block又可分为三个部分:block_title、week_name、day。
可以看到,day的部分固定占六行,每一个日期右对齐并占两位,两个日期之间间隔一个空格,两个block之间间隔两个空格。
每月的首日前和最后一日后的日子要用空格填充。
由此,我们的任务就转化为如何依次输出这三部分了。注意,由于控制台的输出特性,每行block的输出是同时进行的。
可用三重循环来实现,一重循环控制line,一重循环控制block,一重循环控制一行day。
title、block_title、week_name
这三部分打印的内容是固定的,因此可以封装为函数直接调用。
有人会说每个line的block_title不同,其实这里传入一个行数参数即可进行统一控制:
void print_block_title(int i){
switch(i){
case 1:printf(" January February March \n");break;
case 2:printf(" April May June \n");break;
case 3:printf(" July August September \n");break;
case 4:printf(" October November December \n");break;
}
}
day
这块是整道题中最不好打印的地方了。
步骤如下:
- 计算每个月首日是星期几,记为w[i];
- 月首之前空出的日子用空格填充;
- 一次输出同一line中的每一行,共打印6行(分别输出block1、block2、block3中的第一行,再输出第二行,以此类推)。
- 月末之后空出的日子用空格填充。
使用数组d[i]跟踪记录输出时每个block中的最新日期,若输出日期值未超过当前月的最大天数,则输出并增加1;否则输出空格。
使用数组w[i]记录每个block对应月份首日的星期,对应关系为:{周日:0,周一:1,周二:2,周三:3,周四:4,周五:5,周六:6}。
使用数组m[i]记录一个line中各个block对应的月份。
计算月首星期的方法
使用蔡勒公式(Zeller formula)来计算每个月第一天是星期几。(注:蔡勒公式只能计算1582年后的日期)
公式如下:
其中:
w表示星期,取值范围为w={0,1,2,3,4,5,6},0为星期日;
c表示世纪,y表示年分,d表示日;
m表示月份,取值范围为[3,14]内的整数。若被求月份为1月和2月,要分别换算为上一年的13月和14月。
例如有日期2010-01-05,则c=20,y=10,m=13,d=5。
另外,符号“[ ]”表示向下取整。
在利用该公式代值计算时,要特别注意计算中出现负数的情况,建议按照以下步骤进行计算:
先计算,
若w>=0,w = w%7;
否则,w = (w%7+7)%7。
月首和月末日期
每个月第一天之前和最后一天之后的日期要用空格补全。
找规律可知,第一天之前共有w[i]天,最后一天后有7-(w[i]+days[m[i]])%7天,其中days[i]表示第i月的天数,其它数组含义同上所述。
代码
这里的代码是我自己写的(已AC),不是标程。
#include<stdio.h>
int days[13]={-1,31,-1,31,30,31,30,31,31,30,31,30,31};//每月的天数
void print_block_title(int i){
switch(i){
case 1:printf(" January February March \n");break;
case 2:printf(" April May June \n");break;
case 3:printf(" July August September \n");break;
case 4:printf(" October November December \n");break;
}
}
void print_week_name(){
printf("Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa\n");
}
void print_title(int y){
printf(" %d \n\n",y);
}
/**
判断是否闰年
**/
int is_leap_year(int y){
if(y%4==0 && y%100!=0 || y%400==0){
return 1;
}else{
return 0;
}
}
int max_r(int a, int b,int c){
if(a>=b && a>=c){
return a;
}
if(b>=a && b>=c){
return b;
}
if(c>=a && c>=b){
return a;
}
}
/**
计算某个月的第一天是星期几
return:星期(0:星期一,以此类推)
note:使用蔡勒公式。1,2月要当成上一年的13,14月计算。
**/
int day_of_first(int y,int m){
if(m==1 || m==2){
y--;
m += 12;
}
int c = y/100;
y %=100;
int w = y+y/4+c/4-2*c+(26*(m+1)/10);
if(w<0){
w = (w%7+7)%7;
}else{
w %= 7;
}
return w;
}
int main(){
int Y;
while(scanf("%d",&Y)!=EOF){
//title
print_title(Y);
//block
int line_i,j;
int w[3]; //每个月的第一天是星期几(0:星期日)
int d[3]; //累计日期
int m[3]; //月份
for(line_i=1;line_i<=4;line_i++){ //循环打印每一行
//初始化2月天数
if(is_leap_year(Y)) {
days[2] = 29;
}else{
days[2] = 28;
}
//初始化w、d、m数组
int i;
for(i=0;i<3;i++) {
d[i] = 1;
m[i] = (line_i-1)*3+i+1;
w[i] = (day_of_first(Y,m[i]))%7;
}
//计算行数:不用算了,都是6行
// int r1 = ((7-(w[0]+days[m[0]])%7)+w[0]+days[m[0]])/7;
// int r2 = ((7-(w[1]+days[m[1]])%7)+w[1]+days[m[1]])/7;
// int r3 = ((7-(w[2]+days[m[2]])%7)+w[2]+days[m[2]])/7;
// int rows = max_r(r1,r2,r3);
print_block_title(line_i);
print_week_name();
int block_i,row_i;
for(row_i=0;row_i<6;row_i++){
for(block_i=0;block_i<3;block_i++){
if(block_i>0){
printf(" ");
}
for(j=0;j<7;j++){
if(j>0){
printf(" ");
}
if(row_i==0 && j<w[block_i]){//填充空格 :只有第一行才需要填充空格
printf(" ");
}else if(row_i>0 || row_i==0 && j>=w[block_i]){
if(d[block_i]<=days[m[block_i]]){
printf("%2d",d[block_i]);
d[block_i]++;
}else{
printf(" ");
}
}
}
}
printf("\n") ;
}
}
}
return 0;
}