Luogu P2179 [NOI2012] 骑行川藏 (拉格朗日乘数法)(黑题祭!!)

首先:第一道手撕的黑题祭!2022.4.20  

题意:

在满足 $\sum_{i=1}^{n} {k_{i} \times s_{i} \times (v_{i} - v_{i}{}' )^2} \le E_{U} $ 的条件下,

使得 $\sum_{i=1}^{n} {\frac{s_{i}}{v{i}} } $ 最小,

其中 $k_{i},s_{i},v_{i}{}'$ 为给定的常数,未知量就是 $v_{i}$。

分析:

其实学过拉格朗日乘数法的已经能看出来了,求这种多元函数的极值,有了给定的约束条件,直接拉乘就可以了。

那么拉格朗日乘数法是什么呢?

首先你得学会导数,只需要记住几个基本函数的求导法则就行了,比如幂函数,指数函数,对数函数,$sin$,$cos$ ,再学会函数加和、乘积、作比的求导法则,基本够用了。

其实从高等数学的角度来讲,我不会,如果好奇可以去这里

 所以从通俗角度来讲,我们只需要掌握套路,知道这个东西可以求极值就行了。

 那么套路是什么呢?

首先题中给出了约束条件,也就是未知量的一个等式,在本题中,我们可以贪心的想:

我们消耗的体力越多,肯定跑的越快,也就是那个式子越小。

所以我们不妨令约束条件为 $\sum_{i=1}^{n} {k_{i} \times s_{i} \times (v_{i} - v_{i}{}' )^2} = E_{U}$。

接下来引出拉格朗日乘数法最核心的部分:

 我们要求上面式子的极值,就要引入一个 $\lambda $,

然后得到这样的一个函数:

$\sum_{i=1}^{n} {\frac{s_{i}}{v{i}} }  + \lambda (\sum_{i=1}^{n} {k_{i} \times s_{i} \times (v_{i} - v_{i}{}' )^2} - E_{U})$

观察一下是怎么得来的?

首先把要求极值的式子抄过来,再写一个 $\lambda$,再用它乘上约束条件的量都移项到左边的样子。

接下来我们要对这个函数中的每一个变量,也就是我们要求的 $v_{i}$ 和 $\lambda$,求偏导

偏导是什么?我不会啊啊啊!!!

不,你会!

偏导其实就是只考虑当前一个变量,把其他所有的无关变量以及原有的常量统一认为是常量,然后求导。

所以我们推导一下:

对于 $v_{i}$:

首先忽略与他无关的常数式子,得到:

${\frac{s_{i}}{v_{i}} }  + \lambda \times {k_{i} \times s_{i} \times (v_{i} - v_{i}{}' )^2}$

此时进行求导,可以得到:

$-\frac{s_{i}}{v_{i}^2}  + 2\times \lambda \times {k_{i} \times s_{i} \times (v_{i} - v_{i}{}' )}$

注意这里有个小技巧就是 

如果 $f(x) = (x + b)^2$ 那么 $f{}’(x) = 2(x + b)$,手推一下就知道了。

接下来对 $\lambda$ 求导就没什么了,把系数抄下来就OK:

$\sum_{i=1}^{n} {k_{i} \times s_{i} \times (v_{i} - v_{i}{}' )^2} - E_{U}$

众所周知,函数求极值在导函数的零点处取得,所以我们要让所有的 $v_{i}$ 还有 $\lambda$ 都使得他们的导函数为 $0$,也就是说我们所求的式子的极值,在我们求偏导得出的所有式子同时为 $0$ 时取得。

接下来考虑怎么求:

首先我们发现,直接找每一个 $v_{i}$,如同我们脱裤子放屁,是不可行的。

我们看看对 $v_{i}$ 导数的式子可以得到什么?

$2\times \lambda \times {k_{i} \times {v_{i}^2} \times (v_{i} - v_{i}{}' )} = 1$ 

非常滴amazing啊,这不反比例吗?

当然不全是,至少我们肯定能看出来随着 $\lambda$ 递增,$v_{i}$ 肯定递减,且这个时候的 $v_{i}$ 最便于确定。

好!那就二分 $\lambda$ 吧!

然后在二分答案的时候,在里面二分 $v_{i}$ 就好了,因为内外都有单调性,都可以二分,这样我们肯定能逼近到所有

方程都为 $0$ 也就是取到极值的时候啦。

代码部分,其实和之前的神犇好像都差不多啦,重在理解学习!

#include<cstdio>
#include<queue>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cctype>
#include<vector>
#include<string>
#include<climits>
#include<stack>
using namespace std;
template <typename T>
inline void read(T &x){
    x=0;char ch=getchar();bool f=0;
    while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    if(f)x=-x;
}
template <typename T,typename ...Args>
inline void read(T &tmp,Args &...tmps){read(tmp);read(tmps...);}
const double eps = 1e-12;
const int N = 1e4 + 5;
int n;
double E,ans;
double s[N],k[N],v[N],u[N];
inline bool check(double lambda,double v,double k,double u){
    return 2 * lambda * k * v * v * (v - u) <= 1;//检查关于v[i]的导数是否符合条件
}
inline double po(double x){return x * x;}
inline bool check1(double lambda){
    double res = 0;
    for(int i=1;i<=n;++i){
        //puts("1");
        double l = max(u[i],0.0),r = 1e5;
        while(l + eps <= r){//二分v[i]
            double mid = (l + r) / 2;
            if(check(lambda,mid,k[i],u[i]))l = mid;
            else r = mid;
        }
        v[i] = l;
        res += k[i] * po(v[i] - u[i]) * s[i];
    }    
    return res <= E;//这是关于λ的导数方程的检验
    
}
signed main(){
    read(n);
    scanf("%lf",&E);
    for(int i=1;i<=n;++i)scanf("%lf%lf%lf",&s[i],&k[i],&u[i]);//别问为什么v'用u,问就是图论做多了
    double l = 0,r = 1e5;
    while(l + eps <= r){//二分λ
        double mid = (l + r) / 2;
        if(check1(mid))r = mid;
        else l = mid;    
    }
    for(int i=1;i<=n;++i)ans += s[i] / v[i];
    printf("%.8lf",ans);
}

 

posted @ 2022-04-21 15:41  Xu_brezza  阅读(81)  评论(2编辑  收藏  举报