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

Address

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

Solution

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

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

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

约束条件:
ϕ(v1,v2,...,vn)=EUi=1nki(vivi)2si=0 ϕ ( v 1 , v 2 , . . . , v n ) = − E U ∑ i = 1 n k i ( v i − v i ′ ) 2 s i = 0

引入系数 λ λ ,构造拉格朗日函数:
i=1n(sivi+λki(vivi)2si)EU ∑ i = 1 n ( s i v i + λ k i ( v i − v i ′ ) 2 s i ) − E U

vi v i 求偏导得到:
siv2i+λki(2vi2vi+v2i)si − s i v i 2 + λ k i ( 2 v i − 2 v i ′ + v i ′ 2 ) s i

要求上式为 0 0
1vi2+λki(2vi2vi)=0

2λki(vivi)v2i=1 2 λ k i ( v i − v i ′ ) v i 2 = 1

易得上式左边在 vivi v i ≥ v i ′ 即上式左边的值大于等于 0 0 时单调递增。
故在 λ 确定时二分答案可以算出 vi v i
同时, λ λ 越大, vi v i 越小。
所以 λ λ 的值也可以二分,通过 ϕ(v1,v2,...,vn) ϕ ( v 1 , v 2 , . . . , v n ) 的值来调整 λ λ
最后得出了 λ λ 的值之后就能计算取到极值时的 vi 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值