zoukankan      html  css  js  c++  java
  • 【题解】穿越七色虹

    题目背景

    在 Nescafe27 和 28 中,讲述了一支探险队前往 Nescafe 之塔探险的故事……

    当两位探险队员以最快的时间把礼物放到每个木箱里之后,精灵们变身为一缕缕金带似的光,簇簇光芒使探险队员们睁不开眼睛。待一切平静下来之后,探险队员来到了一座宫殿中,玉制的石椅上坐着两个人。「你们就是……Nescafe 之塔护法中的两位?」

    「是的,我们就是神刀护法 xlk 和飞箭护法 riatre……你们来这里做什么?」

    「我们是前来拜访圣主和四位护法的……」

    「如果你们想见圣主和其它两位护法,你们必须穿过前方的七色彩虹。请随我来吧……」

    题目描述

    探险队员们跟随两位护法来到了七色虹前。七色虹,就是平面直角坐标系中赤、橙、黄、绿、青、蓝、紫七个半圆。第 (i) 座半圆形彩虹的圆心是 ((x_i,0)),半径是 (r_i),半圆上所有点的纵坐标均为非负数。探险队员可以看做一条竖直的,长度等于身高的线段。线段的底端纵坐标为 (0),最高的一位探险队员的身高为 (h)

    现在探险队员们要从 ((0,0)) 穿越七色虹到达 ((x_0,0))。穿越七色虹的过程中,探险队员的整个身体必须始终在至少一个半圆形彩虹的内部。由于彩虹的半径 (r_i) 可能太小了,不足以满足这个条件,因此两位护法决定帮助他们把所有彩虹的半径都增大一个非负实数 (r)。探险队员们想知道,(r) 最小是多少呢?

    输入格式

    第一行两个实数 (h)(x_0),表示身高和目的地横坐标。

    接下来七行每行两个实数 (x_i)(r_i),表示七座半圆形彩虹的圆心和半径。

    输出格式

    输出最小的 (r),四舍五入保留 (2) 位小数。

    数据范围

    评测时间限制 (500 extrm{ms}),空间限制 (512 extrm{MiB})

    对于 (100\%) 的数据,(0le x_i,x_0le 10^4)(0<h<100)(1le ile 7)

    分析

    我们只有彻底抓住这道题的所有性质,才能比较好地找出适合的算法。

    (100 exttt{pts})

    根据我们的思考,我们发现以下几个结论:

    1. 对于给定的 (r),我们可以在 (mathcal{O}(1)) 的时间内判断是否合法。
    2. 答案是单调的,也就是说如果 (r^prime) 是合法的,那么 (forall r^{primeprime}>r^prime) 都是合法的。

    根据这几点性质,我们考虑二分答案。复杂度为 (mathcal{O}(log x_0)),常数大约为 (7 imes log_2 100=40),可以通过本题。

    Code

    // @author 5ab
    
    #include <algorithm>
    #include <cstdio>
    #include <cmath>
    using namespace std;
    
    const double EPS = 1e-4; // 防止缺精度多取几位。
    const int max_n = 7; // 颜色数
    
    /*
    判断是否合法的标志:区间合并,判断 [0, x0] 是否在一个连续区间内。
    复杂度是 O(nlogn),其中 n 是颜色数。这个复杂度实际上是排序的复杂度。
    注意不能开始时排序,因为随着半径的变化,顺序也是有可能变化的。
    对于半径为 r,身高为 h,圆心位于 xi 的园的区间为 [xi-sqrt(r^2-h^2), xi+sqrt(r^2-h^2)],可以由勾股定理得到。
    */
    
    struct interval
    {
    	double lp, rp;
    	
    	bool operator<(const interval& it)
    	{
    		if (rp != it.rp)
    			return rp < it.rp;
    		else
    			return lp < it.lp;
    	}
    };
    
    /*
    变量解释:
    it[i]:第 i 个圆所对应的区间;
    rad[i]:第 i 个圆的半径;
    pos[i]:第 i 个圆的位置;
    hei:最高身高;
    ed:终点坐标。
    */
    
    interval it[max_n];
    double rad[max_n], pos[max_n], hei, ed;
    
    bool check(double added)
    {
    	double tmp;
    	
    	for (int i = 0; i < max_n; i++)
    	{
    		tmp = sqrt((rad[i] + added) * (rad[i] + added) - hei * hei);
    		
    		it[i].lp = pos[i] - tmp;
    		it[i].rp = pos[i] + tmp;
    	}
    	
    	sort(it, it + max_n); // 统计区间并排序
    	
    	for (int i = max_n - 1; i > 0; i--) // 合并区间
    	{
    		if (it[i-1].rp < it[i].lp) // 无法合并了
    		{
    			if (it[i].lp <= 0 && it[i].rp >= ed) // 判断是否包含
    				return true; // 是的就退出
    			
    			continue; // 否则继续
    		}
    		
    		if (it[i-1].lp > it[i].lp)
    			it[i-1].lp = it[i].lp;
    		it[i-1].rp = it[i].rp; // 合并区间,取较大的左端点
    	}
    	
    	if (it[0].lp <= 0 && it[0].rp >= ed)
    		return true; // 最后一个特判
    	else
    		return false; // 没有被完全包含的区间,r 还不够大
    }
    
    int main()
    {
    	double lb = 0, ub = 10010, mid, ans = -1;
    	
    	scanf("%lf%lf", &hei, &ed);
    	for (int i = 0; i < max_n; i++)
    		scanf("%lf%lf", pos + i, rad + i); // 输入
    	
    	while (ub - lb > EPS) // 二分,不解释
    	{
    		mid = (lb + ub) / 2;
    		
    		if (check(mid))
    			ans = mid, ub = mid; // 试图寻找更小的答案
    		else
    			lb = mid + EPS; // 一定要加这个 EPS,否则会死循环
    	}
    	
    	printf("%.2lf
    ", ans);
    	
    	return 0;
    }
    

    后记

    这道题是一道很好的二分答案练手题,如果要练习二分答案可以做一下。

    虽然这道题有计算几何的味道,但仔细想想还是一道二分答案题。

    有时,我们不要被其包装所误导,真正重要的是其内在法则。

  • 相关阅读:
    [LeetCode] Wildcard Matching, Solution
    [LeetCode] Add Binary 解题报告
    [LeetCode] Validate Binary Search Tree 解题报告
    [LeetCode] ZigZag Conversion 解题报告
    [LeetCode] Best Time to Buy and Sell Stock II Solution
    [LeetCode] Anagrams 解题报告
    [LeetCode] Word Search 解题报告
    【转载】git/github初级运用自如
    关于实训的那点事儿
    【转载】解决git Push时请求username和password,而不是sshkey验证
  • 原文地址:https://www.cnblogs.com/5ab-juruo/p/solution-20200411-rainbow.html
Copyright © 2011-2022 走看看