[BZOJ2876][Noi2012]骑行川藏(二分答案+拉格朗日乘数法)
程序员文章站
2024-03-17 16:52:28
...
Address
https://www.lydsy.com/JudgeOnline/problem.php?id=2876
Solution
为了方便某些萌新,讲下拉格朗日乘数法。
作用:求多元函数 在约束 下的极值。引入变量 ,令拉格朗日函数:
对 的每个变量(包括 )分别求偏导。
(事实上对 求偏导后的结果就是 )
(多元函数 对一个自变量 的偏导数,定义为将 除 之外的自变量全部视为常数后对 求导)
当 对每个自变量的偏导数都等于 时,
在约束 下取得极值。
回到原问题。
发现体力不是一个标准的约束。
但如果体力没用完,则一定可以在某一天速度更快。
所以,存在一种最优方案使得体力恰好用完。
同样我们也能得到,存在一种最优方案使得对于每个 有 。
( 表示第 天实际速度)
目标函数:
约束条件:
引入系数 ,构造拉格朗日函数:
对 求偏导得到:
要求上式为 。
易得上式左边在 即上式左边的值大于等于 时单调递增。
故在 确定时二分答案可以算出 。
同时, 越大, 越小。
所以 的值也可以二分,通过 的值来调整 。
最后得出了 的值之后就能计算取到极值时的 。
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;
}