HDU.1006 Tick and Tick

一、题目解读

1、原题

HDU.1006 Tick and Tick

2、分类

杂题

3、题意

求[连续运动的]时钟、在 1 1 天内满足条件“任意两个指针夹角大于角度 D D ”的总时间占 1 1 天的百分比。

4、输入输出格式

输入/输出 要求与格式
输入样例个数 通过输入 D = 1 D=-1 标识输入结束
输入格式(每个样例) 每行输入一个数 D D
输出格式(每个样例) 每行输出一个结果
输出精度 结果精确到小数点后三位

5、数据范围

数据 范围
D D 0 D 120 , D R 0 \leq D \leq 120, D \in \mathbb{R}

二、题解参考

1、总体思路

思路 时间复杂度 具体解释
穷举时分,累加秒长 O ( 1 ) O(1) (每个案例将近 1 0 4 10^4 次语句执行) 穷举12个h、60个m,在每组确定的(h, m)下的 1 m i n 1min 内,找出满足题意的可行区间

2、思路①

(1).分析

很显然,时钟表面上只会显示12个小时,因此一天中我们也只需要考虑半天。

给定 h h (时)、 m m (分)、 s s (秒),不难得出三者对应指针以12点方向为起点的角度 { d e g _ h = 30 h + 1 2 m + 1 120 s d e g _ m = 6 m + 1 10 s d e g _ s = 6 s \begin{cases} deg\_h = 30h + \frac{1}{2}m + \frac{1}{120}s \\ deg\_m = 6m + \frac{1}{10}s \\ deg\_s = 6s \end{cases}

由此,很容易得出三个角度差 { Δ h m = 30 h + ( 1 2 6 ) m + ( 1 120 1 10 ) s Δ h s = 30 h + 1 2 m + ( 1 120 6 ) s Δ m s = 6 m + ( 1 10 6 ) s \begin{cases} \Delta hm = 30h + (\frac{1}{2} - 6)m + (\frac{1}{120} - \frac{1}{10})s \\ \Delta hs = 30h + \frac{1}{2}m + (\frac{1}{120} - 6)s \\ \Delta ms = 6m + (\frac{1}{10} - 6)s \end{cases}

根据题目的要求、加上我们最开始的简化,我们需要找到半天之内所有满足以下条件的时间段: { D Δ h m 360 D D Δ h s 360 D D Δ m s 360 D \begin{cases} D \leq | \Delta hm | \leq 360 - D \\ D \leq | \Delta hs | \leq 360 - D \\ D \leq | \Delta ms | \leq 360 - D \end{cases}

秒针每一分钟就会转动一圈,显然 Δ h s \Delta hs Δ m s \Delta ms 都会在 0 360 0 \sim 360 之间快速变化,所以满足题述角度的临界时间会特别多,因此想要直接根据角度、寻找某个公式来求出所有的临界情况是几乎不可能的。

考虑到时钟的指针是连续运动的,因此无法穷举所有时、分、秒。但是仔细思考一下,可以知道,虽然秒不可以穷举,但是时、分还是可以穷举的,只要把时、分确定了,这一分钟内秒的范围也就很容易求出来了(只需要解线性不等式组)。

对于每个不等式,我们一般可以求出两组区间,然后求并集。再把每一个不等式的解求一个交集,就可以知道总体的解区间了。虽然这样做逻辑上很合理,但是要实现却不容易,因为并集并出来的可能是空集,可能是单个连续区间,也可能是多个连续子区间。这样的区间概念不方便实现。

于是我们想到了一个办法进行优化——将三个不等式视为三个组,每组有两个区间解,只要将每组中选一个出来,求出三个区间的交集,遍历所有组合,将时间累积起来即可。

(2).AC代码

HDU(C++/G++)AC代码如下:

#include <iostream>
#include <iomanip>
#include <algorithm>

using namespace std;

double D;

struct interval
{
	double l;
	double r;
};

// 计算满足线性不等式组【D <= as + b <= 360-D】s的解区间,并且要和区间[0, 60]求交集
interval solve(double a, double b)
{
	// 解不等式组
	interval tmp;
	if (a > 0)
	{
		tmp.l = (D - b) / a;
		tmp.r = (360 - D - b) / a;
	}
	else
	{
		tmp.l = (360 - D - b) / a;
		tmp.r = (D - b) / a;
	}

	// 将解区间和[0, 60]求交集
	if (tmp.l < 0)
		tmp.l = 0;
	if (tmp.r > 60)
		tmp.r = 60;

	// 排除其他解(例如[-3, -2]、[65, 70]这样的)
	if (tmp.l >= tmp.r)
		tmp.l = tmp.r = 0;

	return tmp;
}

// 求两个区间的交集
interval intersection(interval a, interval b)
{
	interval tmp;
	tmp.l = max(a.l, b.l);
	tmp.r = min(a.r, b.r);
	if (tmp.l >= tmp.r)
		tmp.l = tmp.r = 0;
	return tmp;
}

// 计算某分钟内的happytime(单位:s)
double get_Happytime(int h, int m)
{
	// deg_h = 30 * h + 0.5 * m + s /120.0
	// deg_m = 6 * m + 0.1 * s
	// deg_s = 6 * s

	double a, b;
	interval us[3][2];
	// △hm = 30 * h - 6.5 * m + (1.0 / 120 - 0.1)s
	a = 1.0 / 120 - 0.1;
	b = 30 * h - 5.5 * m;
	us[0][0] = solve(a, b);
	us[0][1] = solve(-a, -b);

	// △hs = 30 * h + 0.5 * m + (1.0 / 120 - 6)s
	a = 1.0 / 120 - 6;
	b = 30 * h + 0.5 * m;
	us[1][0] = solve(a, b);
	us[1][1] = solve(-a, -b);

	// △ms = 6 * m + (0.1 - 6) * s
	a = 0.1 - 6;
	b = 6 * m;
	us[2][0] = solve(a, b);
	us[2][1] = solve(-a, -b);

	// 对三大组不等式组的解区间、每组出一个区间求三个区间的交集,计算时间(单位:s)
	interval tmp;
	double res = 0;
	for (int i = 0; i < 2; ++i)
		for (int j = 0; j < 2; ++j)
			for (int k = 0; k < 2; ++k)
			{
				tmp = intersection(intersection(us[0][i], us[1][j]), us[2][k]);
				res += (tmp.r - tmp.l);
			}

	return res;
}

int main()
{
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);

	while (cin >> D, fabs(D + 1) > 1e-6)
	{
		double res = 0;
		for (int h = 0; h < 12; ++h)
			for (int m = 0; m < 60; ++m)
				res += get_Happytime(h, m);

		cout << fixed << setprecision(3) << res * 100 / (12 * 60 * 60) << endl;
	}

	return 0;
}

三、总结与吐槽

1、评价

这道题主要考验一些思维。

2、吐槽

说实话,这道题的标题设计在我看来也是有点儿坑的。

题目名字“Tick and Tick”,译为“滴答滴答”,那还不就应该是秒针一秒跳一下的那种指针吗?直接 12 × 60 × 60 12 \times 60 \times 60 穷举完了不就好了(虽然这样的话就真成了一道水题了)。

但是题目的数据却又表明,这是一个秒针连续运动的时针……

3、后话

哎,Markdown老是设计字体、颜色什么的好心累,黑底皮肤也感觉怪怪的,还是使用最常见的皮肤和格式吧。

懒了,随缘写题解吧,网课太多快肝不动了。
慌张

发布了49 篇原创文章 · 获赞 9 · 访问量 3121

猜你喜欢

转载自blog.csdn.net/qq_44220418/article/details/104621383