「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$ 位.
输入输出样例
说明
样例说明
一种可能的方案是:蛋蛋在三段路上都采用匀速骑行的方式,其速度依次为$5.12939919,8.03515481,6.17837967$ .
评分方法
本题没有部分分,你程序的输出只有和标准答案的差距不超过$10^{-6}$ 时,才能获得该测试点的满分,否则不得分.
数据规模与约定
对于$10\%$ 的数据,$n=1$ ;
对于$40\%$ 的数据,$n\le2$ ;
对于$60\%$ 的数据,$n\le100$ ;
对于$80\%$ 的数据,$n\le1000$ ;
对于$100\%$ 的数据,$n\le10^4,E_U\le10^8$
$s_i\in(0,10^5],k_i\in(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\),就可以少写一个字母了):
那么
然后二分公共导数\(x\),对于每段路解方程\(-\frac{s}{2kv^2(v-v')} = x\)(可二分)得到\(v\),进而求出需要的能量。
时间复杂度\(O(n\log^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\n",ans);
return 0;
}