洛谷 P3291 [SCOI2016] 妖怪
设每只怪物经过环境影响后的攻击力和防守力分别为 \(x_i, y_i\),则有:
\(y_i = dnf_i - \dfrac ba(x_i -atk_i)\)。
设 \(k = -\dfrac ba\),则有 \(y_i= kx_i + dnf_i - k \cdot atk_i\)。
设直线 \(l_i : y_i= kx_i + dnf_i - k \cdot atk_i\),第 \(i\) 只怪物在 \((a, b)\) 的环境下的战斗力为 \(f_i(k)\),则有:
再来探究 \(f_i(k)\) 的实质:不难发现是 \(l_i\) 的纵横截距之和。
那 \(l_i\) 是什么呢?
回到我们最初的式子:\(y_i= kx_i + dnf_i - k \cdot atk_i\),此时把 \(dnf_i\) 移到右侧,再在右侧提个 \(k\) 出来,就恰巧构成了一个一次函数的点斜式,即:
所以 \(l_i\) 就是一条过点 \((atk_i, dnf_i)\) 的斜率为 \(k\) 的直线。
设直线 \(l : y = kx + b(b \in \R)\) 与 \(V = \{(atk_i, dnf_i) | i \in [1, n]\}\) 的上凸包相切,那么,\(\max\{f_i(k)\}\) 也就可以理解为 \(l\) 的纵横截距之和。
接下来考虑对于每个单独的点 \((atk_i, dnf_i)\),何时其为 \(l\) 与 \(V\) 的上凸包的切点。
设 \(k_1, k_2\) 分别为凸包上第 \(i - 1\) 个点与第 \(i\) 个点、第 \(i\) 个点与第 \(i + 1\) 个点的斜率,则当且仅当 \(k \in [k_2, k_1]\) 时,\(f_i(k)\) 为切点。
再考虑 \(f_i(k)\) 何时取得最小值,由均值不等式得当且仅当 \(k = -\sqrt{\dfrac{dnf_i}{atk_i}}\) 时,\(f_i(k)\) 取得最小值。
所以,计算答案的方式如下:
- 若 \(-\sqrt{\dfrac{dnf_i}{atk_i}} \in [k_2, k_1]\),则 \(ans = \min\{ans, f_i(-\sqrt{\dfrac{dnf_i}{atk_i}})\}\)。
- 否则,\(ans = \min\{ans, f_i(k_2), f_i(k_1)\}\)。
时间复杂度 \(O(n \log n)\)。
#include <bits/stdc++.h>
#define MAXN 1000100
using namespace std;
int n;
int top, stk[MAXN];
struct Node {
int atk, dnf;
double mink;
double f(double k) {
return atk + dnf - atk * k - dnf / k;
}
Node operator-(const Node &rhs) const {
return {atk - rhs.atk, dnf - rhs.dnf};
}
long long operator^(const Node &rhs) const {
return 1ll * atk * rhs.dnf - 1ll * rhs.atk * dnf;
}
bool operator<(const Node &rhs) const {
if (atk == rhs.atk) return dnf > rhs.dnf;
return atk < rhs.atk;
}
} p[MAXN];
double getk(Node x, Node y) {
return 1.0 * (x.dnf - y.dnf) / (x.atk - y.atk);
}
template<typename _T>
void read(_T &_x) {
_x = 0;
_T _f = 1;
char _ch = getchar();
while (_ch < '0' || '9' < _ch) {
if (_ch == '-') _f = -1;
_ch = getchar();
}
while ('0' <= _ch && _ch <= '9') {
_x = (_x << 3) + (_x << 1) + (_ch & 15);
_ch = getchar();
}
_x *= _f;
}
int main() {
read(n);
for (int i = 1; i <= n; i++) {
read(p[i].atk), read(p[i].dnf);
p[i].mink = -sqrt(1.0 * p[i].dnf / p[i].atk);
}
sort(p + 1, p + n + 1);
for (int i = 1; i <= n; i++) {
while (top > 1 && ((p[i] - p[stk[top - 1]]) ^ (p[stk[top]] - p[stk[top - 1]])) <= 0) top--;
stk[++top] = i;
}
double ans = min(p[stk[top]].f(min(p[stk[top]].mink, getk(p[stk[top - 1]], p[stk[top]]))), p[stk[1]].f(max(p[stk[1]].mink, getk(p[stk[1]], p[stk[2]]))));
for (int i = 2; i < top; i++) {
double k1 = getk(p[stk[i - 1]], p[stk[i]]), k2 = getk(p[stk[i]], p[stk[i + 1]]);
if (k2 <= p[stk[i]].mink && p[stk[i]].mink <= k1) ans = min(ans, p[stk[i]].f(p[stk[i]].mink));
else ans = min(ans, min(p[stk[i]].f(k2), p[stk[i]].f(k1)));
}
printf("%.4lf", ans);
return 0;
}