[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('\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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
NOI普及组考试的难度适中。 首先,NOI普及组考试的题目设计会相对简单一些,以便初学者能够更好地理解和解答。相比于NOI提高组考试的更高难度,普及组考试更注重基础知识和算法的应用。这样不仅使得考生能够更容易掌握考试内容,也能够更好地巩固和拓展自己的计算机科学基础。 其次,考试题目会涵盖计算机科学的各个方面,包括数据结构、算法、编程和理论等。这样可以让考生对计算机科学的不同领域都有所了解,并培养他们的综合能力和解决问题的能力。 此外,NOI普及组考试对编程语言的要求相对宽松。考生可以使用自己熟悉的编程语言进行解题。这样不仅给予考生更多选择的权利,也能更好地发挥他们的编程能力。 然而,虽然NOI普及组考试的难度相对较低,但仍然需要考生具备一定的计算机知识和编程基础。要取得好的成绩,考生需要对常用的数据结构和算法有一定的了解和掌握,并且能够熟练地进行编程实现。同时,考生还需要培养自己的逻辑思维和问题解决能力,以便能够高效地解答问题。 综上所述,NOI普及组考试的难度适中,既考查了考生的计算机科学基础知识和编程能力,又给予了初学者足够的实践机会和提升空间。这样不仅为考生提供了学习和发展的平台,也能够激发他们对计算机科学的兴趣和热情。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值