「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;
}
原文地址:https://www.cnblogs.com/autoint/p/11236996.html