算法竞赛中常见日期问题的求法

前言:一般算法比赛中出现的关于日期的问题基本都是模拟,做这种题的时候只要时间别超时,代码逻辑越简单越好,反正都不会超时干嘛要折腾自己,而且如果代码写复杂了有些逻辑没理清楚那不就尴尬了,所以我感觉做这种题的时候首先先要把题目的意思理解清楚,然后用自己认为简单的代码把题目的意思模拟出来就好了。
我们用简单的几个例子来说说吧!!!



回文日期

在日常生活中,通过年、月、日这三个要素可以表示出一个唯一确定的日期。

牛牛习惯用 8 位数字表示一个日期,其中,前 4 位代表年份,接下来 2 位代表月份,最后 2 位代表日期。

显然:一个日期只有一种表示方法,而两个不同的日期的表示方法不会相同。

牛牛认为,一个日期是回文的,当且仅当表示这个日期的8位数字是回文的。

现在,牛牛想知道:在他指定的两个日期之间(包含这两个日期本身),有多少个真实存在的日期是回文的。

一个 8 位数字是回文的,当且仅当对于所有的 i(1≤i≤8) 从左向右数的第i个数字和第 9−i 个数字(即从右向左数的第 i 个数字)是相同的。

例如:

•对于2016年11月19日,用 8 位数字 20161119 表示,它不是回文的。

•对于2010年1月2日,用 8 位数字 20100102 表示,它是回文的。

•对于2010年10月2日,用 8 位数字 20101002 表示,它不是回文的。

输入格式
输入包括两行,每行包括一个8位数字。

第一行表示牛牛指定的起始日期date1,第二行表示牛牛指定的终止日期date2。保证date1和date2都是真实存在的日期,且年份部分一定为4位数字,且首位数字不为0。
保证date1一定不晚于date2。

输出格式
输出共一行,包含一个整数,表示在date1和date2之间,有多少个日期是回文的。

输入样例:
20110101
20111231
输出样例:
1

分析:首先我们明白题目是要我们求从data1~data2之间有几个回文串,那么我们能不能这样想:从data1直接枚举到data2把日期当作一个8位数,直接进行枚举,但是这样我们发现这样效率就有点低了(要循环data2-data1次),但是我们其实可以发现我们其实只要枚举四位数即可(从1000-9999只要枚举不到10000次就好)由4位数推导出这个4位数对应的回文串比如1000-》10000001然后再判断我们搞的这个回文串是不是在data1和data2之间再判断这个回文串中的日期和天数是否合法即可。

  • 枚举1000~9999
  • 由4位数拓展出回文串
  • 判断月份和天数的合法性

代码:

#include<iostream>
#include<sstream>
#include<algorithm>
#include<string>
using namespace std;
int n1, n2;
int res;
//记录月份的天数
int mouth[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };

//闰年平年判断
bool yearbool(int year) {
	if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)return true;
	return false;
}


bool check(int year) {
	long long temp=year;
	while (year) {
		int t = year % 10;
		temp=temp * 10 + t;
		year /= 10;
	}
	
	//temp就是整个回文串,直接和n1,n2进行大小比较直接把日期当作一个8位数
	if (temp<n1 || temp>n2)return false;
	stringstream s;
	string str;
	s << temp;
	s >> str;

	//判断月份
	int mouth1 = (str[4] - '0') * 10 + (str[5] - '0');
	if (mouth1 <= 0 || mouth1>12)return false;
	//为什么不要判断天数==0呢?因为我们是从1000开始搞回文所以不可能为0
	//判断天数
	int day = (str[6] - '0') * 10 + (str[7] - '0');

    if(yearbool&&mouth1==2&&day>29)return false;
    if(!yearbool&&mouth1==2&&day>28)return false;
    if(mouth1!=2&&day>mouth[mouth1])return false;
	return true;

}
int main() {
	
	cin >> n1 >> n2;
	//直接枚举1000到9999就好因为我们用这4位数
	//然后直接枚举这四位数的后面的4位使其成为回文数
	//然后就看是不是在指定范围内
	//再判断日期合法性
	for (int i = 1000; i <= 9999; i++) {
		if (check(i))res ++ ;
	}
	cout << res << endl;
	return 0;
}

在这里插入图片描述



日期问题

小明正在整理一批历史文献。这些历史文献中出现了很多日期。

小明知道这些日期都在1960年1月1日至2059年12月31日。

令小明头疼的是,这些日期采用的格式非常不统一,有采用年/月/日的,有采用月/日/年的,还有采用日/月/年的。

更加麻烦的是,年份也都省略了前两位,使得文献上的一个日期,存在很多可能的日期与其对应。

比如02/03/04,可能是2002年03月04日、2004年02月03日或2004年03月02日。

给出一个文献上的日期,你能帮助小明判断有哪些可能的日期对其对应吗?

输入格式
一个日期,格式是”AA/BB/CC”。

即每个’/’隔开的部分由两个 0-9 之间的数字(不一定相同)组成。

输出格式
输出若干个不相同的日期,每个日期一行,格式是”yyyy-MM-dd”。

多个日期按从早到晚排列。

数据范围
0≤A,B,C≤9

输入样例:
02/03/04
输出样例:
2002-03-04
2004-02-03
2004-03-02

分析:这一题第一点是要明白题目对于时间的表示,02/03/04是怎么变成后面的输出样例的,第二点就是要怎么枚举才会更加的简单和清晰,我们会发现如果我们直接去列举输入的数变换出来的日期我们的判断合法性会比较复杂:在搞年份的时候是19**还是20**这都要考虑,那么变换出来的种类就太多了,如果是要简单的话我们能不能直接枚举1960年1月1日至2059年12月31日,就直接把他们当成8位数直接枚举,然后再进行判断,先看代码吧,这样干说还是有点不好懂

代码:

#include<iostream>
using namespace std;

int mouth[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
bool check(int year, int mouth1, int day) {
	bool runnian = false;
	if (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)) runnian = true;
	if (mouth1 > 12 || mouth == 0)return false;
	if ((day==0)||(mouth1 != 2 && day > mouth[mouth1]))return false;
	if (mouth1 == 2 && day > mouth[2] + runnian)return false;
	return true;
}
int main() {
	int year,mouth,day;
	scanf("%d/%d/%d", &year, &mouth, &day);  //格式化输入year=2,mouth=3,day=4
	for (int i = 19600101; i <= 20591231; i++) {
		int a, b, c;
		a = i / 10000;
		b = i % 10000 / 100;
		c = i % 10000 % 100;
		if (check(a, b, c)) {
			if ((a % 100 == year && b == mouth && c == day) ||
				a % 100 == day && b == year && c == mouth ||
				a % 100 == day && b == mouth && c == year)
				printf("%02d-%02d-%02d\n", a, b, c);  //不足两位就补零
			
		}
	
	}

	return 0;
}


在这里插入图片描述


总结:

我们在处理时间相关问题的时候尽量去处理成简单的逻辑,不容易出错的能把情况都枚举到的代码,还有一些题目需要我们算出从h1:m1:s1到h2:m1:s2(时分秒的形式)的某些东西(比如算出对对应的差值),像这样的题目我们就可以把时分秒全部转换成秒进行统一计算。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值