题解 P5413 【[YNOI2019]骑单车】
分析
1
加速时,显然小明一定会用最快的加速度;然后,小明一定会在较高的速度下保持尽量长的时间,所以要尽量缩短减速时间,因此减速时也会用最大加速度。
为了更好地证明这一观点,可以作小明的
2
高中运动学公式。对于匀加速直线运动,有:
证明:
去分母即得上公式。
3
小明会让自己的速度尽量大,但是各种客观条件会限制他的速度的上限。
具体来说,在某一段路上,除了不能超过限速之外,还要保证小明可以不超过限速地进入之后的路。也就是,不能发生下面的情况:
-
小明在这段路上一直以最大速行驶,结果一进入下一段路就超速了;
-
小明在这一段路上的速度过快,之后无论再怎么减速,也会在某段路(不一定是下一段)上超速。
显然,为了不在之后的路上超速,小明在每段路的末尾都会有一个最大的允许速度
显然,小明在每段路的开端也都会有一个最大的可能速度
4
如果确定了一段路的
经过上面的分析,
其中
第三式(以加速的情况为例)的原理是:左式对应一直加速所需的路程,右侧对应实际路程。显然,中间有某一部分不加速,或者先加速又减速,都会使对应路程更长。
5
由于没有一定的优先顺序,可以先把
显然,如果还未得到满足所有约束的
之后再算出各段路内部花费的时间即可。
6
一段路内部的情况只可能有两种:
-
小明从初速加速到限速,然后按限速行驶,最后减速到末速。
-
小明从初速加速到某一速度后不得不立即减速,最后减速到末速。
可以先分别算出从初速加速到限速和从限速减速到末速所需的路程
第一种情况容易解决:
对于第二种情况,设加速到的最大的速度为
而
代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
int t,n;
double len[111],lim[111],a[111];
double vl[111],vr[111];
double ans;
double tv,tx1,tx2;//临时用的变量.
bool flag;
int main()
{
scanf("%d",&t);
vr[0]=0;//必须从0开始加速.
while(t--){
scanf("%d",&n);
vl[n+1]=1111;//方便约束 vr[i] 和 vl[i+1] 的关系.
for(int i=1;i<=n;++i){
scanf("%lf%lf%lf",&len[i],&lim[i],&a[i]);
vl[i]=vr[i]=lim[i];
}
do{//缩小 vl 和 vr.
flag=0;
for(int i=1;i<=n;++i){
if(vl[i]>vr[i-1]) vl[i]=vr[i-1],flag=1;
if(vr[i]>vl[i+1]) vr[i]=vl[i+1],flag=1;
if(vl[i]<vr[i]){//用较小的约束较大的,而不是相反.
tv=sqrt(vl[i]*vl[i]+2.0*a[i]*len[i]);//
if(vr[i]>tv) vr[i]=tv,flag=1;
}
else{
tv=sqrt(vr[i]*vr[i]+2.0*a[i]*len[i]);
if(vl[i]>tv) vl[i]=tv,flag=1;
}
}
}while(flag) ;//flag==0 说明无法继续收缩.
ans=0;
for(int i=1;i<=n;++i){
tx1=(lim[i]*lim[i]-vl[i]*vl[i])/(2.0*a[i]);
tx2=(lim[i]*lim[i]-vr[i]*vr[i])/(2.0*a[i]);
if(tx1+tx2>len[i]){//无法加速到限速.
tv=sqrt(a[i]*len[i]+(vl[i]*vl[i]+vr[i]*vr[i])*0.5);//此处 tv 即文章中 vm
ans+=(tv+tv-vl[i]-vr[i])/a[i];
}
else{//可以加速到限速.
ans+=(lim[i]+lim[i]-vl[i]-vr[i])/a[i]+(len[i]-tx1-tx2)/lim[i];
}
}
cout<<ans;
}
return 0;
}