zoukankan      html  css  js  c++  java
  • 「NOI2012」骑行川藏

    「NOI2012」骑行川藏

    题目描述

    蛋蛋非常热衷于挑战自我,今年暑假他准备沿川藏线骑着自行车从成都前往拉萨.

    川藏线的沿途有着非常美丽的风景,但在这一路上也有着很多的艰难险阻,路况变化多端,而蛋蛋的体力十分有限,因此在每天的骑行前设定好目的地,同时合理分配好自己的体力是一件非常重要的事情.

    由于蛋蛋装备了一辆非常好的自行车,因此在骑行过程中可以认为他仅在克服风阻做功(不受自行车本身摩擦力以及自行车与地面的摩擦力影响).

    某一天他打算骑$n$ 段路,每一段内的路况可视为相同:对于第$i$ 段路,我们给出有关这段路况的$3$ 个参数$s_i,k_i,v_i'$ ,其中$s_i$ 表示这段路的长度,$k_i$ 表示这段路的风阻系数,$v_i'$ 表示这段路上的风速($v_i'gt 0$ 表示在这段路上他遇到了顺风,反之则意味着他将受逆风影响).

    若某一时刻在这段路上骑车速度为$v$ ,则他受到的风阻 大小为$F=k_i(v-v_i')^2$ (这样若在长度为$s$ 的路程内保持骑行速度$v$ 不变,则他消耗能量(做功)$E=k_i(v-v_i')^2s$ ).

    设蛋蛋在这天开始时的体能值是$E_U$ ,请帮助他设计一种行车方案,使他在有限的体力内用最短的时间到达目的地。请告诉他最短的时间$T$ 是多少.

    输入输出格式

    输入格式:

    第一行包含一个正整数$n$ 和一个实数$E_U$ ,分别表示路段的数量以及蛋蛋的体能值.

    接下来$n$ 行分别描述$n$ 个路段,每行有$3$ 个实数$s_i,k_i,v_i'$ 分别表示第$i$ 段路的长度,风阻系数以及风速.

    输出格式:

    输出一个实数$T$ ,表示蛋蛋到达目的地消耗的最短时间,要求至少保留到小数点后$6$ 位.

    输入输出样例

    输入样例#1: 复制
    3 10000
    10000 10 5
    20000 15 8
    50000 5 6
    输出样例#1: 复制
    12531.34496464

    说明

    样例说明

    一种可能的方案是:蛋蛋在三段路上都采用匀速骑行的方式,其速度依次为$5.12939919,8.03515481,6.17837967$ .

    评分方法

    本题没有部分分,你程序的输出只有和标准答案的差距不超过$10^{-6}$ 时,才能获得该测试点的满分,否则不得分.

    数据规模与约定

    对于$10\%$ 的数据,$n=1$ ;

    对于$40\%$ 的数据,$nle2$ ;

    对于$60\%$ 的数据,$nle100$ ;

    对于$80\%$ 的数据,$nle1000$ ;

    对于$100\%$ 的数据,$nle10^4,E_Ule10^8$

    $s_iin(0,10^5],k_iin(0,15],v_i'in(-100,100)$ .

    数据保证最终的答案不会超过$10^5$ .

    提示

    必然存在一种最优的体力方案满足:蛋蛋在每段路上都采用匀速骑行的方式.

    RabbitHu的题解

    考虑(E=k_i(v-v_i')^2s)(T=frac{s}{v})的函数图像,我们不难有以下思路。

    感性思路

    一开始,我们可以给所有路段分配一个无穷小的速度。

    接下来,我们需要在一些路段上耗费一定能量用来提速,以此缩短一定时间。不同路段上,花费单位能量能缩短的时间(简称“性价比”)是不同的,所以如果我们要模拟这个过程,一定是每时每刻都在当前性价比最高的路段上花费能量,直到能量花完为止。(似乎……也可以花费负的能量,增加某路段所需时间,然后把能量用到别的地方去。)

    注意到一个性质:随着花费能量增加,性价比会越来越低。

    这样的话,只要按照上面这种贪心策略,时时刻刻在性价比最高的路段花费能量(并使它的性价比降低),最后达到最优解时,各路段性价比会一样。

    暴力模拟似乎是写不出来的,考虑更正常的做法。

    这个性价比是什么呢?如果我们对每段路画出一个(t-E)函数图象,表示该路段需要的时间(t)与花费的能量(E)的函数关系,那么花费一定能量(e)之后的“性价比”是什么呢?就是函数图像上横坐标为(e)处切线的斜率——导数。

    那么最优解就满足——各路段导数一样!

    同时,这个公共导数(是负的)绝对值越小(性价比越低),所需能量越多,总时间越小。

    于是二分这个导数,求出每段速度,以此求出所需能量,和手里的总能量比较一下,就可以二分得到答案了!

    数学推导

    要求出每段导数关于(v)的关系。

    对于一段路来说(方便起见,把(k)乘上(s)作为新的(k),就可以少写一个字母了):

    [E = k(v - v')^2$$ $$t = frac{s}{v} ]

    那么

    [frac{dt}{dE}=frac{dt}{dv} / frac{dE}{dv}\ = -frac{s}{v^2} / 2k(v - v')\ = -frac{s}{2kv^2(v-v')}]

    然后二分公共导数(x),对于每段路解方程(-frac{s}{2kv^2(v-v')} = x)(可二分)得到(v),进而求出需要的能量。

    时间复杂度(O(nlog^2 v))

    #include<bits/stdc++.h>
    #define co const
    #define il inline
    template<class T>T read(){
    	T x=0,w=1;char ch=getchar();
    	for(;!isdigit(ch);ch=getchar())if(ch=='-') w=-w;
    	for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
    	return x*w;
    }
    template<class T>T read(T&x){
    	return x=read<T>();
    }
    using namespace std;
    
    co int N=10000+1;
    int n;
    double E,s[N],k[N],u[N];
    
    double getv(double x,int i){
    	double l=max(u[i],double(0)),r=1e5,mid;
    	for(int cnt=60;cnt--;){
    		mid=(l+r)/2;
    		if(2*k[i]*x*mid*mid*(mid-u[i])>-s[i]) l=mid;
    		else r=mid;
    	}
    	return (l+r)/2;
    }
    double calc(double x){
    	double sum=0;
    	for(int i=1;i<=n;++i){
    		double v=getv(x,i);
    		sum+=k[i]*(v-u[i])*(v-u[i]);
    	}
    	return sum;
    }
    int main(){
    	scanf("%d%lf",&n,&E);
    	for(int i=1;i<=n;++i) scanf("%lf%lf%lf",s+i,k+i,u+i),k[i]*=s[i];
    	double l=-1e9,r=0,mid;
    	for(int cnt=100;cnt--;){
    		mid=(l+r)/2;
    		if(calc(mid)<=E) l=mid;
    		else r=mid;
    	}
    	mid=(l+r)/2;
    	double ans=0;
    	for(int i=1;i<=n;++i) ans+=s[i]/getv(mid,i);
    	printf("%.18lf
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    [Android]Android开发艺术探索第13章笔记
    [Android]Android性能优化
    [设计模式]单例模式
    [设计模式]享元模式
    [Android]《Android艺术开发探索》第一章读书笔记
    1.传入:”Welome to Beijing”改为 “Beijing to Welcome” 将字符串按照单词进行逆序,空格作为划分单词的唯一条件
    关于ubuntu.18.04的安装注意事项
    String和Date,Timestamp之间的转换
    判断图片色彩模式 CYMK || RGB
    java copy file
  • 原文地址:https://www.cnblogs.com/autoint/p/11236996.html
Copyright © 2011-2022 走看看