HDOJ1006 Tick and Tick题解

HDOJ1006 Tick and Tick题解

离散化模拟

现实中的钟表受限于精度,秒针走动是一顿一顿的。虽然题目中的钟表指针是连续走动的,但是由于所求答案只是精确到小数点后三位,所以仍然有可能以离散的指针跳动,然后统计happy time的比率。就看采样精度是否足够。因此有了如下离散化模拟的代码:

#include <cstdio>
#include <cstdlib>
#include <cmath>
#define SECONDS_IN_HALF_DAY (12 * 60 * 60)
// 采样精度相对1秒的倍数
#define SAMPLE_RATIO 1
/*
采样精度的选择:
从1开始,查看是否满足样例的输出,如果不是那么加倍;
重复这个过程,直到遇到第一个满足样例输出的精度;
这时采用二分法,找到最小的满足样例输出的精度。
*/

int main()
{
    double minHappyDegree;
    double maxHappyDegree; // maxHappyDegree = 360 - minHappyDegree
    double secondHandDegree;
    double minuteHandDegree;
    double hourHandDegree;
    int totalTicks = SECONDS_IN_HALF_DAY * SAMPLE_RATIO;
    int happyTicks;

    while(1)
    {
        scanf("%lf", &minHappyDegree);

        if(minHappyDegree == -1.0)
        {
            break;
        }

        happyTicks = 0;
        maxHappyDegree = 360.0 - minHappyDegree;

                         for(int tick = 0; tick < totalTicks; tick++)
        {
            //secondHandDegree = (double)tick / SAMPLE_RATIO / 60 * 360;
            secondHandDegree = (double)tick / SAMPLE_RATIO * 6.0;
            secondHandDegree = fmod(secondHandDegree, 360.0);
            //minuteHandDegree = (double)tick / SAMPLE_RATIO / 60 / 60 * 360;
            minuteHandDegree = (double)tick / SAMPLE_RATIO / 10.0;
            minuteHandDegree = fmod(minuteHandDegree, 360.0);
            //hourHandDegree = (double)tick / SAMPLE_RATIO / 60 / 60 / 12 * 360;
            hourHandDegree = (double)tick / SAMPLE_RATIO / 120.0;
            hourHandDegree = fmod(hourHandDegree, 360.0);
            double dsm = fabs(secondHandDegree - minuteHandDegree);
            double dsh = fabs(secondHandDegree - hourHandDegree);
            double dmh = fabs(minuteHandDegree - hourHandDegree);

            if(dsm >= minHappyDegree && dsm <= maxHappyDegree &&
                    dsh >= minHappyDegree && dsh <= maxHappyDegree &&
                    dmh >= minHappyDegree && dmh <= maxHappyDegree)
            {
                happyTicks++;
            }
        }

        printf("%.3lf\n", (double)happyTicks / totalTicks * 100.0);
    }

    return EXIT_SUCCESS;
}

问题在于,虽然这样子可以解出来样例输入数据,但是运行超时!我们不得不寻找其他办法。

连续的时间

如果时间是连续的,那么可以设自变量 t ∈ [0, 60*60*12),单位为秒,那么有

  • 秒针在时刻t相对12点转过的角度为:ωs(t) = 360/60 * t = 6t
  • 分针在时刻t相对12点转过的角度为:ωm(t) = 360/3600 * t = t/10
  • 时针在时刻t相对12点转过的角度为:ωh(t) = 360/(3600*12) * t = t/120

因为是周期性的运动,所以我们可以以运动最快的秒针运动一圈为考察单位,将[0, 60*60*12)每隔60划分为720个考察单位。我们的运气很好,在这样子的考察单位里,不存在指针跨越12点的情况,这意味着将角度规约化到[0,360),它们依然是连续的。

为什么说指针跨越12点会不连续呢?比如有一根针从359°运动到361°,我们将其规约化所造成的结果就是指针从359°运动到360°以及从0°运动到1°,不得不细分为两个区间来考察。所以前面所说的考察单位的选择其实是受不能跨越12点这个条件限制的。

接下来,在每一个考察区间([t0, t0 + 60))里面,我们需要统计happy time的时间,也就是解关于t的不等式组:

t0 < t < t0 + 60
D < |normal(ωs(t)) - normal(ωm(t))| 且 D < 360 - |normal(ωs(t)) - normal(ωm(t))|
D < |normal(ωm(t)) - normal(ωh(t))| 且 D < 360 - |normal(ωm(t)) - normal(ωh(t))|
D < |normal(ωs(t)) - normal(ωh(t))| 且 D < 360 - |normal(ωs(t)) - normal(ωh(t))|

t0 < t < t0 + 60
D < |normal(ωs(t)) - normal(ωm(t))| < 360 -D
D < |normal(ωm(t)) - normal(ωh(t))| < 360 -D
D < |normal(ωs(t)) - normal(ωh(t))| < 360 -D

因为normal操作在给定的考察区间里面,本质上就是减去360的整数倍,而这个减数在整个考察区间里面都不会发生变化,所以像 normal(ωs(t)) - normal(ωm(t)) 这样子的式子化简之后就是 k*t + b 的形式。

接下来就是解含有绝对值符号的不等式的问题了,对 k*t + b 分正负情况讨论即可消去绝对值符号。

综上,将每个考察区间的子happy time累加,除以总时间再乘以100即得happy time所占百分比了。

附代码如下:

package pid1006;

import java.math.RoundingMode;
import java.text.DecimalFormat;

public class Main {
    public static final int MAX_T = 60 * 60 * 12;
    static java.util.Scanner stdin = new java.util.Scanner(System.in);
    static java.io.PrintWriter stdout = new java.io.PrintWriter(System.out);

    public static void main(String[] args) {
        DecimalFormat format = new DecimalFormat("0.000");
        format.setRoundingMode(RoundingMode.HALF_UP);
        while (true) {
            String line = stdin.nextLine();
            if (line.equals("-1")) {
                break;
            }
            Double degree = new Double(line);
            double percent = solve(degree);
            String result = format.format(percent);
            stdout.println(result);

        }
        stdout.flush();
    }

    /**
     * 解关于t的不等式: a < |k * t + b| < c
     * 
     * @param a
     * @param k
     *            不为0
     * @param b
     * @param c
     * @param result 
     *            返回解区间
     */
    private static void solveInequation(double a, double k, double b, double c,
            double mint, double maxt, Interval[] result) {
        // 当 k*t + b >= 0时
        // 有 a < k*t + b < c
        // 即 (a-b)/k < t < (c-b)/k
        Interval intervalA = new Interval(-b / k, maxt);
        Interval intervalB = new Interval((a - b) / k, (c - b) / k);
        Interval intervalC = Interval.intersect(intervalA, intervalB);
        // 当 k*t + b < 0 时
        // 有 -c < k * t + b < -a
        // 即 (-c-b)/k < t < (-a-b)/k
        Interval intervalD = new Interval(mint, -b / k);
        Interval intervalE = new Interval((-c - b) / k, (-a - b) / k);
        Interval intervalF = Interval.intersect(intervalD, intervalE);
        result[0] = intervalC;
        result[1] = intervalF;

    }

    private static double solve(Double degree) {
        Interval[] arr_sm = new Interval[2];
        Interval[] arr_mh = new Interval[2];
        Interval[] arr_sh = new Interval[2];
        double length = 0.0;
        for (int t = 0; t < MAX_T; t += 60) {
            int omega_s_normalizer = normalizer(t, 6, 1);
            int omega_m_normalizer = normalizer(t, 1, 10);
            int omega_h_normalizer = normalizer(t, 1, 120);
            solveInequation(degree, 59.0 / 10.0, -omega_s_normalizer
                    + omega_m_normalizer, 360 - degree, t, t + 60, arr_sm);
            solveInequation(degree, 11.0 / 120.0, -omega_m_normalizer
                    + omega_h_normalizer, 360 - degree, t, t + 60, arr_mh);
            solveInequation(degree, 719.0 / 120.0, -omega_s_normalizer
                    + omega_h_normalizer, 360 - degree, t, t + 60, arr_sh);

            for (int i = 0; i < 2; i++) {
                Interval ism = arr_sm[i];
                for (int j = 0; j < 2; j++) {
                    Interval jmh = arr_mh[j];
                    Interval intersect = Interval.intersect(ism, jmh);
                    for (int k = 0; k < 2; k++) {
                        Interval ksh = arr_sh[k];
                        Interval tmp = Interval.intersect(intersect, ksh);
                        length += (tmp == null) ? 0.0 : tmp.length();
                    }
                }
            }
        }
        return length / MAX_T * 100.0;
    }

    /**
     * 计算角度规约化所需减去的度数
     * 
     * @param t
     *            时间
     * @param numerator
     *            系数的分子
     * @param denominator
     *            系数的分母
     * @return 360 的整数倍
     */
    private static int normalizer(int t, int numerator, int denominator) {
        numerator = t * numerator;
        denominator = denominator * 360;
        return (numerator / denominator) * 360;
    }

}

/**
 * 数学概念上的区间,此处不关心开闭
 * 
 */
class Interval {
    public double left;
    public double right;

    public Interval(double left, double right) {
        this.left = left;
        this.right = right;
    }

    /**
     * 求两个区间的交区间
     * 
     * @param a
     * @param b
     * @return 两个区间的交区间
     */
    public static Interval intersect(Interval a, Interval b) {
        if (a == null) {
            return null;
        }
        if (b == null) {
            return null;
        }
        if (a.length() <= 0.0 || b.length() <= 0.0) {
            return new Interval(0.0, 0.0);
        }
        double left = a.left > b.left ? a.left : b.left;
        double right = a.right < b.right ? a.right : b.right;
        if (right - left >= 0) {
            return new Interval(left, right);
        }
        return null;
    }

    /**
     * 求区间长度
     * 
     * @return
     */
    public double length() {
        return right - left;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值