「NOI2012」骑行川藏
这道题被 yhx-12243 吐槽,但是我还是没考虑全所有的细节……
这种数学题也是要尽量去推条件的。
这道题精度要求貌似很高……还是我写丑了?
根据题意,可以列出式子
\[\mathrm{Minimize} \sum \frac{s_i}{v_i} \\
\mathrm{s.t.} \sum k_i s_i \left(v_i - v_i'\right)^2 = E
\]
多元函数求最值,考虑拉格朗日乘数
\[\begin{align*}
g(v) & = \sum k_i s_i \left(v_i - v_i'\right)^2 - E \\
f(v, \lambda) & = \sum \frac{s_i}{v_i} + \lambda g(v)
\end{align*}
\]
解方程
\[\begin{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'\right)^2 & = 0\\
\frac{\partial f(v, \lambda)}{\partial \lambda} & = \sum k_i s_i \left(v_i - v_i'\right)^2 - E & = 0
\end{align*}
\]
对于 \(v_i\) 的偏导,发现 \(2 \lambda k_i \left(v_i - v_i'\right)^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 的。
复杂度 \(O\left( k_1k_2n \right)\),其中 \(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;
}