程序控

IPPP (Institute of Penniless Peasent-Programmer) Fellow

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: :: 管理 ::

Time limit: 3.000 seconds
限时3.000秒

 

Background and Problem
背景与问题

In 45 BC a standard calendar was adopted by Julius Caesar--each year would have 365 days, and every fourth year have an extra day--the 29th of February. However this calendar was not quite accurate enough to track the true solar year, and it became noticeable that the onset of the seasons was shifting steadily through the year. In 1582 Pope Gregory XIII ruled that a new style calendar should take effect. From then on, century years would only be leap years if they were divisible by 400. Furthermore the current year needed an adjustment to realign the calendar with the seasons. This new calendar, and the correction required, were adopted immediately by Roman Catholic countries, where the day following Thursday 4 October 1582 was Friday 15 October 1582. The British and Americans (among others) did not follow suit until 1752, when Wednesday 2 September was followed by Thursday 14 September. (Russia did not change until 1918, and Greece waited until 1923.) Thus there was a long period of time when history was recorded in two different styles.
在公元前45年,凯撒大帝颁布了标准历法——每年有365天,每4年闰一天,闰在2月的29日。然而此历法并不能精确的表示一个太阳年,季节开始地越来越早引起了人们的注意。到1582年,教皇格利戈里八世规定了新的历法,从那以后,所有能被100整除的年份中只有能被400整除的才是闰年。另外,当年的日历要按照新历法进行调整才能与季节吻合,1582年10月4日(周四)的后一天定为1582年10月15日(周五)。新历法和调整方案被罗马天主教国家迅速采用,但英国和美国(还包括其它一些国家)直到1752年才开始采用,是年9月2日(周三)的后一天定为9月14日(周四)。(俄国直到1918才作出调整,而希腊则维持旧历直到1923年)因此有很长一段时期的历史是以两种不同的纪年方式记载的。

Write a program that will read in a date, determine which style it is in, and then convert it to the other style.
写一个程序,读入日期并确定是哪一种历法,然后将该日期转换到另一种历法。

 

Input
输入

Input will consist of a series of lines, each line containing a day and date (such as Friday 25 December 1992). Dates will be in the range 1 January 1600 to 31 December 2099, although converted dates may lie outside this range. Note that all names of days and months will be in the style shown, that is the first letter will be capitalised with the rest lower case. The file will be terminated by a line containing a single '#'.
输入由多行组成,每行包括一个日期(如“Friday 25 December 1992”)。日期的范围由1 January 1600到31 December 2099,转换后得到的日期可不在此限内。注意,所有的星期几和月份的名称都要按所给的样式输出,即第一个字母要大写,后面的字母小写。输入的数据由一个字符串“#”号表示结束。(译注:根据星期几和日期来确定)

 

Output
输出

Output will consist of a series of lines, one for each line of the input. Each line will consist of a date in the other style. Use the format and spacing shown in the example and described above. Note that there must be exactly one space between each pair of fields. To distinguish between the styles, dates in the old style must have an asterisk ('*') immediately after the day of the month (with no intervening space). Note that this will not apply to the input.
输出由多行组成,每行输入对应一行输出。每行均为输入日期在另一历法下的日期。使用上面给出的描述和范例中的格式进行输出。注意,每两个相邻的值之间用空格隔开。为区分新旧历法,输出的日期若为旧历法应在“日”的的后面加一个星号(“*”)。(日和星号之间没有空格)。注意,输入的数据均不带星号。

 

Sample Input
输入示例

Saturday 29 August 1992
Saturday 16 August 1992
Wednesday 19 December 1991
Monday 1 January 1900
#

 

Sample Output
输出示例

Saturday 16* August 1992
Saturday 29 August 1992
Wednesday 1 January 1992
Monday 20* December 1899

 

Analysis
分析

这是我在UVa OJ中第一次碰到有关时间和日期的计算题目。由所给信息可知,旧历的1582年10月5日与新历的1582年10月15日应该是同一天。从旧历的元年1月1日至1582年10月5日的天数为:1581×365 + 1581÷4 + 273 + 15 - 1= 577737。式中273为当年1月到10月的累积天数,后面-1是由于我们将元年的1月1日定为第0天,因此每个月应从0号开始计算。从新历的元年1月1日至1582年10月15日的天数为:1581×365 + 1581÷4 - 1581÷100 + 1581÷400 + 273 + 15 - 1= 577735。我们发现,新历和旧历的元年1月1日并不是同一天,新历的要晚两天。

接下来要做的是判断输入的日期是哪一种历法。看来没有其它办法,只能通过星期几是否和日期相符来判断了。但会不会存在新旧历中两个相同的日期,它们的星期几也相同呢?回答是肯定的,1300年的3月28日在新旧历上都是星期日,2100年的3月1日都是星期一。然而您尽可以放心,在题目所给的范围内:1600年1月1日至2099年12月31日是不存在这样的情况的。

这样思路就理清了,先将日期的星期几计算出来,判定是哪种历法。再转为自元年1月1日至那一天的累计天数,经过较正后(加减2天),用另一种历法反算回来就可以了。首先是计算星期几的算法,存在这样一个事实,不管日期是否连续,星期几永远是连续的(由0到7再回到0)。按新历从现在已知的某一天向前推算,可知新历的元年1月1日(第0天)是周一,那么任何日期转化所得的累计天数加1再除7的余数即是其在新历下的星期几。

接下来就是将累计天数转为指定历法下的日期。如果是要转换到新历法,由于每100年的天数是不一样,因此要先按400年的总天数取整计算,再按100年的总天数取整计算。这个过程用语言描述很困难,还是看代码吧,注释很详尽了。此外,做这道题一定要细心,有很多情况需要考虑。在提交前最好能生成一个几百年的日期表测试一下。

一个从1600-2100年的老黄历,也许能提供帮助。

 

Solution
解答

#include <algorithm>
#include <functional>
#include <iostream>
#include <string>
using namespace std;
//平年和闰年的各月累计天数表
static int aMDays[] =  {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
static int aMDaysL[] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335};
//根据指定的历法判定闰年。bNew为true代表新历法,fase代表旧历法
bool LeapYear(int Year, bool bNew) {
	if (bNew) {
		//标准的闰年判断
		return ((Year % 4 == 0 && Year % 100 != 0) || Year % 400 == 0);
	}
	return (Year % 4 == 0);
}
//将年月日换算为从元年1月1日(第0天)到这一天的天数
int Date2Days(int Year, int Month, int Date, bool bNew) {
	--Year; //年份定为以0年起始
	//年份乘以365天,加上闰年数(总置闰天数)
	//如果为新历法,要减去百年中不能被400整除的置闰天数
	int Days = Year * 365 + Year / 4 + (bNew ? (Year / 400 - Year / 100) : 0);
	//如果为闰年且月分大于2,要闰1天
	Days += ((Month > 2) ? LeapYear(Year + 1, bNew) : 0);
	//返回:年累计天数+月累计天数+日期-新旧历法起点对齐
	return (Days + aMDays[Month - 1] + Date - 1 - (!bNew * 2));
}
//主函数
int main(void) {
	//月分和星期的字符串表,用于处理输入和输出
	const static string aDays[] = {"Sunday", "Monday", "Tuesday",
		"Wednesday", "Thursday", "Friday", "Saturday"};
	const static string aMonths[] = {"January", "February", "March",
		"April", "May", "June", "July", "August", "September",
		"October", "November", "December"};
	//400年的天数,100年的天数,4年的天数和1年的天数常量
	const int nDays400Y = 400 * 365 + 100 - 3;
	const int nDays100Y = 100 * 365 + 25 - 1;
	const int nDays4Y = 4 * 365 + 1, nDays1Y = 365;
	//循环处理每一个输出的日期
	for (string str; cin >> str && str != "#";) {
		int Day, Date, Month, Year;
		//在字符串表中检索是周几
		Day = find(&aDays[0], &aDays[7], str) - &aDays[0];
		//读入年月日
		cin >> Date >> str >> Year;
		//在字符串表中检索是几月
		Month = find(&aMonths[0], &aMonths[12], str) - &aMonths[0] + 1;
		//将当前日期按旧历换算为天数
		int nDays = Date2Days(Year, Month, Date, false);
		bool bOld2New = true; //为真表示由旧历转新历
		//判定算得的星期是否与输入的相符,若相符则为旧历纪年
		if (Day == (nDays + 1) % 7) {
			//转新历时要先处理400年和100年的置闰
			Year = nDays / nDays400Y * 400; //算出第几个400年
			nDays %= nDays400Y;
			//算出400年中的第几个百年
			if (nDays == nDays100Y * 4) { //该400年的最后一天
				Year += 300;
				nDays -= nDays100Y * 3;
			}
			else {
				Year += nDays / nDays100Y * 100;
				nDays %= nDays100Y; //取零头
			}
		} //否则为亲历纪年,重新计算天数,并加2与旧历对齐
		else {
			nDays = Date2Days(Year, Month, Date, true) + 2;
			Year = bOld2New = 0;
		}
		//算出百年中的第几个4年
		Year += nDays / nDays4Y * 4;
		nDays %= nDays4Y; //取零头
		//算出4年中的第几年
		if (nDays == nDays1Y * 4) { //年4年的最后一天
			Year += 3;
			nDays -= nDays1Y * 3;
		}
		else {
			Year += nDays / nDays1Y;
			nDays %= nDays1Y; //取零头
		}
		//判定闰年,选取相应的各月累计天数表
		int *pMDays = (LeapYear(++Year, bOld2New) ? aMDaysL : aMDays);
		//按当年累计天数查找月份
		for (Month = 0; Month < 12 && pMDays[Month] <= nDays; ++Month);
		//计算当月日期
		Date = nDays - pMDays[Month - 1] + 1;
		//计算星期几
		Day = (Date2Days(Year, Month, Date, bOld2New) + 1) % 7;
		//按格式要求输出结果
		cout << aDays[Day] << ' ' << Date << (bOld2New ? " " : "* ");
		cout << aMonths[Month - 1] << ' ' << Year << endl;
	}
	return 0;
}
posted on 2010-08-21 22:51  Devymex  阅读(2069)  评论(2编辑  收藏  举报