• [NOI2012] 骑行川藏 | 求导 二分


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

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


    感性理解一下这道题:

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

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

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

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

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

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

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

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

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


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

    要求出每段导数关于(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('
    ')
    #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
    ", ans);
    
        return 0;
    }
    
  • 相关阅读:
    android 布局中的单位及分辨率自解
    7种例子讲解Android Dialog!
    jqDnR 层拖动插件 潇湘博客
    jQuery选择器热榜
    左边补0 php 潇湘博客
    Javascript代码压缩、加密算法的破解分析及工具实现
    discuz 整合总结
    js 格式化 潇湘博客
    Linux内核网络协议栈深入分析(五)套接字的绑定、监听、连接和断开
    Linux内核基于Netfilter的内核级包过滤防火墙实现
  • 原文地址:https://www.cnblogs.com/RabbitHu/p/9019762.html
Copyright © 2020-2023  润新知