[BZOJ2876][NOI2012]骑行川藏(导数+数学相关)

题目:

我是超链接

题解:

题目就是让求

k i s i ( v i v i ) 2 <= E u
的条件下满足
m i n s i v i

我们先理解一下这道题目:
首先我们可以给所有路段随意分配一个速度
接下来,我们需要在一些路段上 消耗更多能量来提速,以此来 缩短一定的时间。不同的路段,花费单位能量缩短的时间(简称“ 性价比”)是不同的,所以要模拟这个过程,一定是每时每刻都在 当前性价比最高的路段上花费能量,直到能量花完为止。(相对的,我们可以减少某一条路径的能量,增加某路径所需的时间,然后把能量用到别的地方去)

根据前面的式子我们可以发现:随着花费能量的增加,性价比会越来越低

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

这个性价比是什么呢?t-E函数的斜率——导数。那么最优解满足,各路段导数一样

那么我们二分这个导数,求出每段的速度,求出所有的能量,和总能量比较一下,就可以得出答案了

我们要求出每一段导数关于v的关系。(k=s*k)

E = k ( v v ) 2

t = s v

求谁的导数啊?我们考虑变化1单位的速度,t会变化t’,E会变化E’(x’为x的导数),我们要找到t’/E’最大的那个增加速度,也就是要求: t E

t = ( s v ) = s v 2

E = ( k ( v v ) 2 ) = 2 k ( v v )

t E = s 2 k v 2 ( v v )

然后二分公共导数x,对于每段路解方程: s 2 k v 2 ( v v ) = x (二分解方程)得到v,进而求出所需要的能量。

竟然卡我精度= =,开到1e-12,为了防止/0,我们需要在可能出现0的getv函数里返回二分值

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const double eps=1e-12;
const int N=100005;
double s[N],k[N],v[N],E;int n;
double getv(double x,int i)
{
    double l=max(v[i],(double)0),r=(double)N;
    while (r-l>=eps)
    {
        double mid=(l+r)/2;
        if (2*k[i]*mid*mid*(mid-v[i])*x>-s[i]) l=mid;else r=mid;
    }
    return (l+r)/2;
}
double nl(double x)
{
    double sum=0;
    for (int i=1;i<=n;i++)
    {
        double vv=getv(x,i);
        sum+=k[i]*(vv-v[i])*(vv-v[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],&v[i]),k[i]*=s[i];
    double l=-N,r=0;
    while (r-l>=eps)
    {
        double mid=(l+r)/2;
        if (nl(mid)<=E) l=mid;else r=mid; 
    }
    double ans=0;
    for (int i=1;i<=n;i++) ans+=s[i]/getv(l,i);
    printf("%.10lf",ans);
}

猜你喜欢

转载自blog.csdn.net/blue_cuso4/article/details/80509726