这道题被 yhx-12243 吐槽,但是我还是没考虑全所有的细节……
这种数学题也是要尽量去推条件的。
这道题精度要求貌似很高……还是我写丑了?
根据题意,可以列出式子
[mathrm{Minimize} sum frac{s_i}{v_i} \
mathrm{s.t.} sum k_i s_i left(v_i - v_i'
ight)^2 = E
]
多元函数求最值,考虑拉格朗日乘数
[egin{align*}
g(v) & = sum k_i s_i left(v_i - v_i'
ight)^2 - E \
f(v, lambda) & = sum frac{s_i}{v_i} + lambda g(v)
end{align*}
]
解方程
[egin{align*}
frac{partial f(v, lambda)}{partial v_i} & = - frac{s_i}{v_i^2} + 2 lambda k_i s_i left(v_i - v_i'
ight)^2 & = 0\
frac{partial f(v, lambda)}{partial lambda} & = sum k_i s_i left(v_i - v_i'
ight)^2 - E & = 0
end{align*}
]
对于 (v_i) 的偏导,发现 (2 lambda k_i left(v_i - v_i' ight)^2 = 1)。
注意到如果我们的速度 (v_i > v_i')。如果 (v_i < v_i'),那么存在一个 (v = 2v_i' - v_i),那么能量消耗相等,但是速度更快了,明显更优。
于是将这个条件带入方程,发现 (lambda > 0)。
然后发现当 (lambda) 增大, (v_i) 是单调减的。那么 (g(v)) 也会单调减。所以可以二分 (lambda) 来找到一个满足第二条方程式的解
然后将得到的解带入就好了。
关于解出 (v_i),可以二分,也可以迭代。注意迭代的初始解要大,不然会挂。
注意调参,在精度与速度中权衡。如果没写挂也不会爆 nan 的。
复杂度 (Oleft( k_1k_2n ight)),其中 (k_1) 是 (lambda) 二分次数, (k_2) 是解方程迭代次数。
#include <bits/stdc++.h>
const int MAXN = 10010;
const int STP = 30;
const int S2 = 150;
typedef long double ld;
const ld eps = 1e-12;
ld ss[MAXN], ks[MAXN], vs[MAXN];
int n; ld E;
ld solve(int i, ld l) {
ld x = 1e5;
ld c = .5 / ks[i] * l;
for (int S = 0; S < STP; ++S) {
ld func = x * x * (x - vs[i]) - c;
ld de = x * (3 * x - 2 * vs[i]);
x -= func / de;
}
return x;
}
ld tv[MAXN];
inline ld pows(ld x) { return x * x; }
int main() {
std::ios_base::sync_with_stdio(false), std::cin.tie(0);
std::cin >> n >> E;
for (int i = 1; i <= n; ++i) std::cin >> ss[i] >> ks[i] >> vs[i];
ld l = 0, r = 1e10;
for (int S = 1; S <= S2; ++S) {
ld mid = (l + r) / 2;
ld sum = 0;
for (int i = 1; i <= n; ++i) {
tv[i] = solve(i, mid);
sum += ks[i] * ss[i] * pows(tv[i] - vs[i]);
}
sum > E ? r = mid : l = mid;
}
ld ans = 0;
for (int i = 1; i <= n; ++i) ans += ss[i] / tv[i];
std::cout << std::fixed << std::setprecision(10) << ans << std::endl;
return 0;
}