[BZOJ2876][Noi2012]骑行川藏(二分答案+拉格朗日乘数法)

Address

https://www.lydsy.com/JudgeOnline/problem.php?id=2876

Solution

为了方便某些萌新,讲下拉格朗日乘数法。
作用:求多元函数 f ( x 1 , x 2 , . . . , x n ) 在约束 ϕ ( x 1 , x 2 , . . . , x n ) = 0 下的极值。引入变量 λ ,令拉格朗日函数:

F ( x 1 , x 2 , . . . , x n , λ ) = f ( x 1 , x 2 , . . . , x n ) + λ ϕ ( x 1 , x 2 , . . . x n )

F 的每个变量(包括 λ )分别求偏导。
(事实上对 λ 求偏导后的结果就是 ϕ ( x 1 , x 2 , . . . , x n )
(多元函数 F 对一个自变量 x 的偏导数,定义为将 F x 之外的自变量全部视为常数后对 F 求导)
F 对每个自变量的偏导数都等于 0 时,
f ( x 1 , x 2 , . . . , x n ) 在约束 ϕ ( x 1 , x 2 , . . . , x n ) = 0 下取得极值。
回到原问题。
发现体力不是一个标准的约束。
但如果体力没用完,则一定可以在某一天速度更快。
所以, 存在一种最优方案使得体力恰好用完
同样我们也能得到,存在一种最优方案使得对于每个 i v i v i
v i 表示第 i 天实际速度)
目标函数:
f ( v 1 , v 2 , . . . , v n ) = i = 1 n s i v i

约束条件:
ϕ ( v 1 , v 2 , . . . , v n ) = E U i = 1 n k i ( v i v i ) 2 s i = 0

引入系数 λ ,构造拉格朗日函数:
i = 1 n ( s i v i + λ k i ( v i v i ) 2 s i ) E U

v i 求偏导得到:
s i v i 2 + λ k i ( 2 v i 2 v i + v i 2 ) s i

要求上式为 0
1 v i 2 + λ k i ( 2 v i 2 v i ) = 0

2 λ k i ( v i v i ) v i 2 = 1

易得上式左边在 v i v i 即上式左边的值大于等于 0 时单调递增。
故在 λ 确定时二分答案可以算出 v i
同时, λ 越大, v i 越小。
所以 λ 的值也可以二分,通过 ϕ ( v 1 , v 2 , . . . , v n ) 的值来调整 λ
最后得出了 λ 的值之后就能计算取到极值时的 v i

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
using namespace std;
const int N = 1e4 + 5;
const double eps = 1e-12;
int n;
double eu, k[N], s[N], vp[N], ans;
double vsol(double lambda, int i) {
    double l = max(vp[i], 0.0), r = 1e5;
    while (r - l > eps) {
        double mid = (l + r) / 2, val;
        val = 2.0 * lambda * k[i] * (mid - vp[i]) * mid * mid;
        if (val < 1) l = mid;
        else r = mid;
    }
    return (l + r) / 2;
}
double calc(double lambda) {
    int i; double res = 0, tmp;
    For (i, 1, n) tmp = vsol(lambda, i),
        res += k[i] * s[i] * (tmp - vp[i]) * (tmp - vp[i]);
    return res;
}
int main() {
    int i;
    double x, y, lambda, l = 0, r = 1e5;
    cin >> n >> eu;
    For (i, 1, n) scanf("%lf%lf%lf", &s[i], &k[i], &vp[i]);
    while (r - l > eps) {
        double mid = (l + r) / 2;
        if (calc(mid) > eu) l = mid;
        else r = mid;
    }
    For (i, 1, n) ans += s[i] / vsol((l + r) / 2, i);
    printf("%.10lf\n", ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/xyz32768/article/details/81586176
今日推荐