[NOI2012]骑行川藏——拉格朗日乘子法

原题链接
不会啊,只好现学了拉格朗日乘子法,简单记录一下

前置芝士:拉格朗日乘子法

要求\(n\)元目标函数\(f(x_1,x_2,...,x_n)\)的极值,且有\(m\)个约束函数形如\(h_i(x_1,x_2,...,x_n)=0\)
引入松弛变量\(\alpha _1-\alpha _m\),构造拉格朗日函数如下:
\[L(x_1,x_2,...,x_n,\alpha _1,\alpha _2,...,\alpha _m)=f(x_1,x_2,...,x_n)+\sum\limits_{i=1}^{m}\alpha _ih_i(x_1,x_2,...,x_n)\]
然后分别对\(x\)\(a\)求偏导并令偏导值为\(0\)($\nabla $为梯度向量):
\[\nabla _xL(x,\alpha)=0,\nabla _{\alpha}L(x,\alpha)=0\]
求解上述方程组,即可求得极值点。但是解方程组的代价太大了,在做题时我们一般会通过函数的单调性二分来解
为什么可以这样呢,考虑一下,满足条件的极值点应该是在目标函数的等高线与约束函数曲线相切的点,在这一点上有如下等式成立:
\[\nabla _xf(x)=a\nabla _xh(x)\]
而拉格朗日函数求导之后和上式本质相同,因此它能求得最值
还有广义拉格朗日乘子法是适用于有不等式约束的情况

题解

首先我们把目标函数和约束函数都找出来

目标函数 \(f(x)=\sum\limits_{i=1}^{n}\frac{s_i}{v_i}\)

约束函数 \(g(x)=\sum\limits_{i=1}^{n}k_is_i(v_i-v'_i)^2-E_U\)

那么拉格朗日函数为
\[L(x,\alpha)=f(x)+\alpha g(x)=-\alpha E_U+\sum\limits_{i=1}^{n}\frac{s_i}{v_i}+\alpha k_is_i(v_i-v'_i)^2\]
求出 \(v_i\)关于 \(L\)的偏导并将其设置为 \(0\)
\[\frac{\partial L(v,\alpha)}{\partial v_i}=-\frac{s_i}{v_i^2}+2\alpha s_ik_i(v_i-v'_i)=0\]
\[\Rightarrow \alpha=\frac{1}{2k_iv_i^2(v_i-v'_i)}\]
经过简单讨论,可以得出 \(\alpha\)\(v\)单调递减, \(g\)\(v\)单调递增,所以 \(g\)\(\alpha\)单调递减
于是我们可以先二分 \(\alpha\),然后再二分解出 \(v\),复杂度 \(O(nlog^2n)\)
附代码:

#include <bits/stdc++.h>

using namespace std;

#define N 10000

const double eps = 1e-13, INF = 1e5;
int n;
double Eu, s[N + 5], k[N + 5], v0[N + 5], v[N + 5];

bool check(double lamda) {
    for (int i = 1; i <= n; ++i) {
        double tar = 1 / (2 * k[i] * lamda), l = max(v0[i], 0.0), r = INF, mid;
        while(r - l >= eps) {
            mid = (l + r) / 2;
            if (mid * mid * (mid - v0[i]) > tar) r = mid;
            else l = mid;
        }
        v[i] = mid;
    }
    double E = 0;
    for (int i = 1; i <= n; ++i)
        E += k[i] * s[i] * pow(v[i] - v0[i], 2);
    return E <= Eu;
}

int main() {
    cin >> n >> Eu;
    for (int i = 1; i <= n; ++i)
        cin >> s[i] >> k[i] >> v0[i];
    double l = 0, r = INF, mid;
    while (r - l >= eps) {
        mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    double ans = 0;
    for (int i = 1; i <= n; ++i)
        ans += s[i] / v[i];
    cout << setiosflags(ios::fixed) << setprecision(10);
    cout << ans << endl;
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/dummyummy/p/11125711.html