分享

[NOI2012] 骑行川藏 详解(求导 二分)

 长沙7喜 2019-02-20

    一个能看的题解!预备知识只有高中数学的【导数】。不用什么偏导数/拉格朗日乘子法之类的我看不懂的东西( ·̀∀·́ )!

如果你不知道什么是导数,可以找本高中数学选修2-2来看一下!看第一章第1、2节就好啦。

题目描述


蛋蛋非常热衷于挑战自我,今年暑假他准备沿川藏线骑着自行车从成都前往拉萨。川藏线的沿途有着非常美丽的风景,但在这一路上也有着很多的艰难险阻,路况变化多端,而蛋蛋的体力十分有限,因此在每天的骑行前设定好目的地、同时合理分配好自己的体力是一件非常重要的事情。
由于蛋蛋装备了一辆非常好的自行车,因此在骑行过程中可以认为他仅在克服风阻做功(不受自行车本身摩擦力以及自行车与地面的摩擦力影响)。某一天他打算骑N段路,每一段内的路况可视为相同:对于第i段路,我们给出有关这段路况的3个参数 si , ki , vi’ ,其中 si 表示这段路的长度, ki 表示这段路的风阻系数, vi’ 表示这段路上的风速(表示在这段路上他遇到了顺风,反之则意味着他将受逆风影响)。若某一时刻在这段路上骑车速度为v,则他受到的风阻大小为 F = ki ( v – vi’ )2(这样若在长度为s的路程内保持骑行速度v不变,则他消耗能量(做功)E = ki ( v – vi’ )2 s)。
设蛋蛋在这天开始时的体能值是 Eu ,请帮助他设计一种行车方案,使他在有限的体力内用最短的时间到达目的地。请告诉他最短的时间T是多少。
「评分方法」
本题没有部分分,你程序的输出只有和标准答案的差距不超过0.000001时,才能获得该测试点的满分,否则不得分。
「数据规模与约定」
对于10%的数据,N=1;
对于40%的数据,N<=2;
对于60%的数据,N<=100;
对于80%的数据,N<=1000;
对于所有数据,N <= 10000,0 <= Eu <= 108,0 < si <= 100000,0 < ki <= 1,-100 < vi’ < 100。数据保证最终的答案不会超过105。
「提示」
必然存在一种最优的体力方案满足:蛋蛋在每段路上都采用匀速骑行的方式。

Input

第一行包含一个正整数N和一个实数Eu,分别表示路段的数量以及蛋蛋的体能值。 接下来N行分别描述N个路段,每行有3个实数 si , ki , vi’ ,分别表示第 i 段路的长度,风阻系数以及风速。

Output

输出一个实数T,表示蛋蛋到达目的地消耗的最短时间,要求至少保留到小数点后6位。

Sample Input

3 10000
10000 10 5
20000 15 8
50000 5 6

Sample Output

12531.34496464
「样例说明」 一种可能的方案是:蛋蛋在三段路上都采用匀速骑行的方式,其速度依次为5.12939919, 8.03515481, 6.17837967。

题解:

感性理解一下这道题:

一开始,我们可以给所有路段随便分配一个速度。

接下来,我们需要在一些路段上耗费一定能量用来提速,以此缩短一定时间。不同路段上,花费单位能量能缩短的时间(简称“性价比”)是不同的,所以如果我们要模拟这个过程,一定是每时每刻都在当前性价比最高的路段上花费能量,直到能量花完为止。(似乎……也可以花费负的能量,增加某路段所需时间,然后把能量用到别的地方去。)

注意到一个性质:随着花费能量增加,性价比会越来越低。

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

暴力模拟似乎是写不出来的,考虑更正常的做法。

这个性价比是什么呢?如果我们对每段路画出一个tE函数图象,表示该路段需要的时间t花费的能量E的函数关系,那么花费一定能量e之后的“性价比”是什么呢?就是函数图像上横坐标为e处切线的斜率——导数。

那么最优解就满足——各路段导数一样!

同时,这个公共导数(是负的)绝对值越小(性价比越低),所需能量越多,总时间越小。

于是二分这个导数,求出每段速度,以此求出所需能量,和手里的总能量比较一下,就可以二分得到答案了!

以上是思路。现在开始数学。

要求出每段导数关于v的关系。

对于一段路来说(方便起见,把k乘上s作为新的k,就可以少写一个字母了2333):

那么


#include <cstdio>

#include <cstring>

#include <cmath>

#include <algorithm>

#include <iostream>

#define enter putchar('\n')

#define space putchar(' ')

using namespace std;

typedef long long ll;

template <class T>

void read(T &x){

    char c;

    bool op = 0;

    while(c = getchar(), c < '0' || c > '9')

        if(c == '-') op = 1;

    x = c - '0';

    while(c = getchar(), c >= '0' && c <= '9')

        x = x * 10 + c - '0';

    if(op == 1) x = -x;

}

template <class T>

void write(T x){

    if(x < 0) putchar('-'), x = -x;

    if(x >= 10) write(x / 10);

    putchar('0' + x % 10);

}


const int N = 10005, INF = 0x3f3f3f3f;

int n;

double E, s[N], k[N], u[N];


double getv(double x, int i){

    double l = max(u[i], double(0)), r = 100005, mid;

    int cnt = 60;

    while(cnt--){

        mid = (l + r) / 2;

        if(2 * k[i] * x * mid * mid * (mid - u[i]) > -s[i]) l = mid;

        else r = mid;

    }

    mid = (l + r) / 2;

    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 = -INF, r = 0, mid;

    int cnt = 100;

    while(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('%.10lf\n', ans);


    return 0;

}

本文作者胡小兔,原文地址https://www.cnblogs.com/RabbitHu/p/9019762.html,经作者授权发布!

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约