「NOI2012」骑行川藏

这道题被 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;
}
原文地址:https://www.cnblogs.com/daklqw/p/11647974.html