[NOI2012] 骑行川藏

一、题目

点此看题

二、解法

据说导数的相关内容是选择性必修 \(2\) 的,考虑到我也没有学过,那我就简单的讲一讲吧,我只讲怎么求函数 \(y=ax^n\) 的导数:

\[d=\frac{a(x+\Delta x)^n-ax^n}{\Delta x}=?\Delta x^{?}x^{?}+anx^{n-1} \]

因为最后包含 \(\Delta x\) 的项可以视而不见,我们用二项式展开可以得到唯一剩下的项是 \(anx^{n-1}\) ,而他就是导数。如果是若干个上述形式的函数加起来,那么不难得到他们的导数也相加。


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

首先考虑一些消耗能量最少的走法:如果 \(v[i]\) 大于 \(0\) ,那么我们就使 \(v=v[i]\) ;如果 \(v[i]\leq0\) ,那么我们使得 \(v=0\) 。这样显然不是最优的,但是我们可以慢慢的调整使其达到最优,这种方法叫做 微调法

但是这道题是在实数范围内的最优问题,我们无法用最小单位来微调。那么我们不妨用极限的思想,我们把无限小的能量用于给某段路减少时间,那么这道题就必须要引入 导数 的概念。即每段路都拥有一个导数,我们每次取导数最大的微调。

我们可以想象一个 \(E-t\) 的图像 ,这个图像的导数是越来越大的(导数永远是负数,后面投入的相同能量会产生更小的效益),因为微调的过程是不可能实现的,我们可以利用这个性质来达到微调的效果。可以二分一个导数值 \(x\)最后小于等于 \(x\) 的初始导数都会等于 \(x\) ,那么这个取法才是最优的。

如果算出来消耗的能量大于已经有的能量,那么就把导数值调小,否者就把导数值调大。怎么算需要消耗的能量呢?首先我们要知道每段路的导数表达式,那么我们就可以反解出速度。现在我们来推导导数 \(d\) 的表达式:

\[d=\frac{\Delta t}{\Delta E} \]

我们知道 \(t=\frac{s}{v},E=sk(v-v')^2\),发现 \(t\)\(E\) 好像是没有关联的,所以上面的 \(\Delta t\) 就不是很好求,那么我们就用 \(v\) 把他们关联起来,也就是求出 \(v\) 变化很小的时候 \(t\)\(E\) 的变化量,也就是 \(t-v\)\(E-v\) 图像的导数

\[d=\frac{\Delta t/\Delta v}{\Delta E/\Delta v}=\frac{-1sv^{-2}}{2sk(v-v')}=\frac{-1}{2kv^2(v-v')} \]

那么可以用 \(d\) 反解出 \(v\) ,这是个一元三次方程,但是具有单调性,所以可以二分解决。

本题是二分套二分,方便卡时间我们不规定二分的精度而规定二分的次数,外层二分我做了 \(100\) 次,内层二分我做了 \(60\) 次,只要卡着时间你尽量多分几次呗。

最后补充一点,你知不知道提示的匀速骑行的策略是怎么来的。我们假设有一条路上不匀速骑行,那么我们可以把它划分成无限个匀速骑行的段,这些段的 \(k\)\(v'\) 都相同,最后达到的 \(d\) 也相同,那么由于导数的单调性解出来的 \(v\) 也都是相同的,这与一开始的假设相悖。所以我们用反证法说明了这个结论。

#include <cstdio>
#include <iostream>
using namespace std;
#define db double
const int M = 10005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n;db E,s[M],k[M],v[M];
db getv(db x,int i)
{
	db l=max(0.0,v[i]),r=100000,mid;int cnt=60;
	while(cnt--)
	{
		mid=(l+r)/2;
		if(2*k[i]*x*mid*mid*(mid-v[i])>-1) l=mid;
		else r=mid;
	}
	return (l+r)/2;
}
signed main()
{
	n=read();scanf("%lf",&E);
	for(int i=1;i<=n;i++)
	{
		scanf("%lf %lf %lf",&s[i],&k[i],&v[i]);
	}
	db l=-1e10,r=0,mid,sum;int cnt=100;
	while(cnt--)
	{
		mid=(l+r)/2,sum=0;
		for(int i=1;i<=n;i++)
		{
			db x=getv(mid,i);
			sum+=s[i]*k[i]*(x-v[i])*(x-v[i]);
		}
		if(sum<=E) l=mid;
		else r=mid;
	}
	db ans=0;mid=(l+r)/2;
	for(int i=1;i<=n;i++)
	{
		ans+=s[i]/getv(mid,i);
	}
	printf("%.6f\n",ans);
}
posted @ 2020-12-24 21:56  C202044zxy  阅读(118)  评论(0编辑  收藏  举报