[NOI2012] 騎行川藏 | 求導 二分

一個能看的題解!預備知識只有高中數學的【導數】。不用什麼偏導數/拉格朗日乘子法之類的我看不懂的東西( •̀∀•́ )!ios

若是你不知道什麼是導數,能夠找本高中數學選修2-2來看一下!看第一章第一、2節就好啦。傳送門:選修2-2函數


感性理解一下這道題:spa

一開始,咱們能夠給全部路段隨便分配一個速度。code

接下來,咱們須要在一些路段上耗費必定能量用來提速,以此縮短必定時間。不一樣路段上,花費單位能量能縮短的時間(簡稱「性價比」)是不一樣的,因此若是咱們要模擬這個過程,必定是每時每刻都在當前性價比最高的路段上花費能量,直到能量花完爲止。(彷佛……也能夠花費負的能量,增長某路段所需時間,而後把能量用到別的地方去。)get

注意到一個性質:隨着花費能量增長,性價比會愈來愈低。數學

這樣的話,只要按照上面這種貪心策略,時時刻刻在性價比最高的路段花費能量(並使它的性價比下降),最後達到最優解時,各路段性價比會同樣string

暴力模擬彷佛是寫不出來的,考慮更正常的作法。it

這個性價比是什麼呢?若是咱們對每段路畫出一個\(t-E\)函數圖象,表示該路段須要的時間\(t\)花費的能量\(E\)的函數關係,那麼花費必定能量\(e\)以後的「性價比」是什麼呢?就是函數圖像上橫座標爲\(e\)處切線的斜率——導數。io

那麼最優解就知足——各路段導數同樣!class

同時,這個公共導數(是負的)絕對值越小(性價比越低),所需能量越多,總時間越小。

因而二分這個導數,求出每段速度,以此求出所需能量,和手裏的總能量比較一下,就能夠二分獲得答案了!


以上是思路。如今開始數學。

要求出每段導數關於\(v\)的關係。

對於一段路來講(方便起見,把\(k\)乘上\(s\)做爲新的\(k\),就能夠少寫一個字母了2333):

\[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\),進而求出須要的能量。


代碼:

#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;
}
相關文章
相關標籤/搜索