[LibreOJ 2353][NOI2007]货币兑换
emmm做了一下这道神题……(我可能是少有的用动态凸包苟的?)
首先DP方程长这样:
\[f_i = max(f_{i - 1}, f_j\cdot\frac{A_iR_j+B_i}{A_jR_j+B_j})\]
然后这个方程炒鸡复杂……首先\(f_{i - 1}\)不要管了,然后设\(a_i = \frac{f_i}{A_iR_i + B_i}\)。在xjb推了一番之后我们终于得到了截距式……
\[-a_j R_j \frac{A_i}{B_i} + \frac{f_i}{B_i} = a_j\]
但是这玩意太毒瘤了……斜率不可能单调的,这还好,在凸壳上二分/三分一下即可。但问题在于,横坐标也不单调……
这个时候就需要动态维护凸包了(其实是我不会CDQ),我直接把我向量集那题的二进制分组线段树搬了过来……(逃
代码:
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <utility>
#include <vector>
#include <cmath>
#include <climits>
#include <deque>
#include <cassert>
using R = double;
const R eps = 1e-8;
int sign(R x) {
if(fabs(x) < eps) {
return 0;
} else {
if(x > 0.00) {
return 1;
} else {
return -1;
}
}
}
struct Point {
R x, y;
Point(R qx = 0, R qy = 0) {
x = qx; y = qy;
}
};
using Vector = Point;
Vector operator +(const Vector &a, const Vector &b) {
return Vector(a.x + b.x, a.y + b.y);
}
Vector operator -(const Point &a, const Point &b) {
return Vector(b.x - a.x, b.y - a.y);
}
Vector operator *(const Vector &a, R lam) {
return Vector(a.x * lam, a.y * lam);
}
Vector operator *(R lam, const Vector &a) {
return Vector(a.x * lam, a.y * lam);
}
inline R dot(const Vector &a, const Vector &b) {
return (a.x * b.x + a.y * b.y);
}
inline R times(const Vector &a, const Vector &b) {
return (a.x * b.y - a.y * b.x);
}
inline bool cmp(const Point &a, const Point &b) {
if(sign(a.x - b.x) == 0) {
return a.y < b.y;
} else {
return a.x < b.x;
}
}
inline void andrew(Point *P, int L, std::vector<Point> &bot, std::vector<Point> &top) {
std::sort(P + 1, P + 1 + L, cmp);
for(int i = 1; i <= L; i ++) {
if(i != 1 && sign(P[i].x - P[i - 1].x) == 0) continue;
while(bot.size() > 1 && sign(times(P[i] - bot.back(), bot.back() - bot[bot.size() - 2])) >= 0) {
bot.pop_back();
}
bot.push_back(P[i]);
}
for(int i = L; i >= 1; i --) {
if(i != L && sign(P[i].x - P[i + 1].x) == 0) continue;
while(top.size() > 1 && sign(times(P[i] - top.back(), top.back() - top[top.size() - 2])) >= 0) {
top.pop_back();
}
top.push_back(P[i]);
}
std::reverse(top.begin(), top.end());
}
const int maxn = 1000005;
const int N = 1000000;
const int maxno = maxn << 2;
bool zen[maxno];
std::vector<Point> bot[maxno], top[maxno];
Point P[maxn];
inline void maintain(int o, int L, int R) {
static Point tmp[maxn];
const int lc = o << 1, rc = o << 1 | 1;
const bool used = zen[o];
zen[o] = (zen[lc] && zen[rc]);
if(zen[o] != used) {
std::copy(P + L, P + R + 1, tmp + 1);
int len = R - L + 1;
andrew(tmp, len, bot[o], top[o]);
}
}
void modify(int o, int L, int R, const int &p, const Point &v) {
if(L == R) {
zen[o] = true;
P[L] = v;
bot[o].push_back(v); top[o].push_back(v);
} else {
const int M = (L + R) / 2;
if(p <= M) {
modify(o << 1, L, M, p, v);
} else {
modify(o << 1 | 1, M + 1, R, p, v);
}
maintain(o, L, R);
}
}
inline R calc_ans(R k, const Point &v) {
return v.y - k * v.x;
}
inline R search(const std::vector<Point> &vec, const R &k) {
int l = 0, r = vec.size() - 1;
while(r - l > 2) {
int lm = (l * 2 + r) / 3, rm = (2 * r + l) / 3;
if(sign(calc_ans(k, vec[lm]) - calc_ans(k, vec[rm])) == 1) {
r = rm;
} else {
l = lm;
}
}
R ans = INT_MIN;
for(int i = l; i <= r; i ++) {
ans = std::max(ans, calc_ans(k, vec[i]));
}
return ans;
}
R query(int o, int L, int R, const int &ql, const int &qr, const double &k) {
if(ql <= L && R <= qr) {
return search(top[o], k);
} else {
int M = (L + R) / 2;
double ans = INT_MIN;
if(ql <= M) {
ans = std::max(ans, query(o << 1, L, M, ql, qr, k));
}
if(qr > M) {
ans = std::max(ans, query(o << 1 | 1, M + 1, R, ql, qr, k));
}
return ans;
}
}
int n, s;
R A[maxn], B[maxn], Rate[maxn];
R f[maxn];
R dp() {
static double a[maxn];
f[0] = s; f[1] = s; a[1] = f[1] / (A[1] * Rate[1] + B[1]);
modify(1, 1, n, 1, Point(a[1] * Rate[1], a[1]));
for(int i = 2; i <= n; i ++) {
f[i] = query(1, 1, n, 1, i - 1, -A[i] / B[i]) * B[i];
f[i] = std::max(f[i], f[i - 1]);
a[i] = f[i] / (A[i] * Rate[i] + B[i]);
if(i < n) modify(1, 1, n, i, Point(a[i] * Rate[i], a[i]));
}
return f[n];
}
int main() {
scanf("%d%d", &n, &s);
for(int i = 1; i <= n; i ++) {
scanf("%lf%lf%lf", &A[i], &B[i], &Rate[i]);
}
printf("%.3lf\n", dp());
return 0;
}
[LibreOJ 2197][SDOI2014]向量集
xjb写了写……我评测时候心脏跳得贼快(逃
考虑如果知道了那一段区间的凸包那么怎么做。首先如果向量是往上指的话,一定在上凸壳上找点比较好,反之则在下凸壳上找点比较好(放到坐标系里脑补一下?)。然后我们观察一点,在上凸壳上的最优解往两边的点会越来越劣,所以这玩意是个上凸函数,可以三分答案(我才学的整数三分啊)。
但区间凸包求起来复杂度很爆炸啊……考虑用线段树搞?观察到一点,我们区间查询所使用的线段树节点一定是只包含了已经加进来的点。所以说,一个线段树节点的凸包需要被求的情况只有一种,那就是这个节点完全已加入点被覆盖了。那每次修改之后看是否一个节点完全被已加入点覆盖,如果被完全覆盖的话才去求它的凸包。
这样一来,线段树上每个节点之多会被求一次凸包。线段树有\(\log n\)层,每一层所有节点的大小加起来是\(n\),所以求凸包耗费的总复杂度是\(n\log^2 n\)级别的。
其实这就是用线段树模拟二进制分组?
代码:
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <utility>
#include <vector>
#include <climits>
#include <cassert>
using ll = long long;
using T = ll;
struct Point {
T x, y;
Point(T qx = 0LL, T qy = 0LL) {
x = qx; y = qy;
}
};
using Vector = Point;
Vector operator +(const Vector &a, const Vector &b) {
return Vector(a.x + b.x, a.y + b.y);
}
Vector operator -(const Point &a, const Point &b) {
return Vector(a.x - b.x, a.y - b.y);
}
Vector operator *(const Vector &a, T lam) {
return Vector(a.x * lam, a.y * lam);
}
Vector operator *(T lam, const Vector &a) {
return Vector(a.x * lam, a.y * lam);
}
inline T dot(const Vector &a, const Vector &b) {
return (a.x * b.x + a.y * b.y);
}
inline T times(const Vector &a, const Vector &b) {
return (a.x * b.y - a.y * b.x);
}
inline bool cmp(const Point &a, const Point &b) {
if((a.x - b.x) == 0LL) {
return a.y < b.y;
} else {
return a.x < b.x;
}
}
inline void andrew(Point *P, int L, std::vector<Point> &bot, std::vector<Point> &top) {
std::sort(P + 1, P + 1 + L, cmp);
for(int i = 1; i <= L; i ++) {
if(i != 1 && (P[i].x - P[i - 1].x) == 0LL) continue;
while(bot.size() > 1 && (times(P[i] - bot.back(), bot.back() - bot[bot.size() - 2])) >= 0LL) {
bot.pop_back();
}
bot.push_back(P[i]);
}
for(int i = L; i >= 1; i --) {
if(i != L && (P[i].x - P[i + 1].x) == 0LL) continue;
while(top.size() > 1 && (times(P[i] - top.back(), top.back() - top[top.size() - 2])) >= 0LL) {
top.pop_back();
}
top.push_back(P[i]);
}
std::reverse(top.begin(), top.end());
}
const int maxn = 400005;
const int maxno = maxn << 2;
const int N = 400000;
bool zen[maxno];
std::vector<Point> bot[maxno], top[maxno];
Point P[maxn];
inline void maintain(int o, int L, int R) {
static Point tmp[maxn];
const int lc = o << 1, rc = o << 1 | 1;
const bool used = zen[o];
zen[o] = (zen[lc] && zen[rc]);
if(zen[o] != used) {
std::copy(P + L, P + R + 1, tmp + 1);
int len = R - L + 1;
andrew(tmp, len, bot[o], top[o]);
}
}
void modify(int o, int L, int R, const int &p, const Point &v) {
if(L == R) {
zen[o] = true;
P[L] = v;
bot[o].push_back(v); top[o].push_back(v);
} else {
const int M = (L + R) / 2;
if(p <= M) {
modify(o << 1, L, M, p, v);
} else {
modify(o << 1 | 1, M + 1, R, p, v);
}
maintain(o, L, R);
}
}
inline T search(const std::vector<Point> &vec, const Point &p) {
int l = 0, r = vec.size() - 1;
while(r - l > 2) {
int lm = (l * 2 + r) / 3, rm = (2 * r + l) / 3;
if(dot(p, vec[lm]) > dot(p, vec[rm])) {
r = rm;
} else {
l = lm;
}
}
T ans = LLONG_MIN;
for(int i = l; i <= r; i ++) {
ans = std::max(ans, dot(p, vec[i]));
}
return ans;
}
T query(int o, int L, int R, const int &ql, const int &qr, const Point &p) {
if(ql <= L && R <= qr) {
if(p.y > 0LL) {
return search(top[o], p);
} else {
return search(bot[o], p);
}
} else {
int M = (L + R) / 2;
T ans = LLONG_MIN;
if(ql <= M) {
ans = std::max(ans, query(o << 1, L, M, ql, qr, p));
}
if(qr > M) {
ans = std::max(ans, query(o << 1 | 1, M + 1, R, ql, qr, p));
}
return ans;
}
}
inline int decode(int x, long long lastans) {
return x ^ (lastans & 0x7fffffff);
}
int main() {
int q; char buf[4]; scanf("%d%s", &q, buf);
bool typ_E = (buf[0] == 'E' && buf[1] == char(0));
T las = 0LL;
int tot = 0;
while(q --) {
char op[4]; scanf("%s", op);
if(op[0] == 'A') {
T x, y; scanf("%lld%lld", &x, &y);
if(!typ_E) {
x = decode(x, las); y = decode(y, las);
}
tot ++;
modify(1, 1, N, tot, Point(x, y));
} else {
T x, y, l, r; scanf("%lld%lld%lld%lld", &x, &y, &l, &r);
if(!typ_E) {
x = decode(x, las); y = decode(y, las);
l = decode(l, las); r = decode(r, las);
}
las = query(1, 1, N, l, r, Point(x, y));
printf("%lld\n", las);
}
}
return 0;
}
[BZOJ 1857][SCOI2010]传送带
三分套三分入门题……
策略肯定是从A走到AB上一点,然后再走到CD上的一个点,再向D走。
很显然答案函数是一个关于那两个点下凸的东西(不会证?GeoGebra之类的东西画一下就好啦!还不如像我这样口胡),所以我们可以先对第一维三分,然后套上对第二维的三分……
代码:
/**************************************************************
Problem: 1857
User: danihao123
Language: C++
Result: Accepted
Time:244 ms
Memory:820 kb
****************************************************************/
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <utility>
#include <cmath>
typedef double R;
const R eps = 1e-6;
int sign(R x) {
if(fabs(x) < eps) {
return 0;
} else {
if(x < 0) return -1;
else return 1;
}
}
struct Point {
R x, y;
Point(R qx = 0, R qy = 0) {
x = qx; y = qy;
}
};
typedef Point Vector;
Vector operator +(const Vector &a, const Vector &b) {
return Vector(a.x + b.x, a.y + b.y);
}
Vector operator -(const Point &a, const Point &b) {
return Vector(a.x - b.x, a.y - b.y);
}
Vector operator *(R x, const Vector &v) {
return Point(v.x * x, v.y * x);
}
Vector operator *(const Vector &v, R x) {
return Point(v.x * x, v.y * x);
}
R dot(const Vector &a, const Vector &b) {
return a.x * b.x + a.y * b.y;
}
R times(const Vector &a, const Vector &b) {
return a.x * b.y - a.y * b.x;
}
R dist(const Point &a, const Point &b) {
return sqrt(dot(a - b, a - b));
}
bool cmp(const Point &a, const Point &b) {
if(sign(a.x - b.x) == 0) {
return a.y < b.y;
} else {
return a.x < b.x;
}
}
Point A, B, C, D;
R p, q, r;
Vector D_AB, D_DC;
R f(const Point &AB, const Point &CD) {
return (dist(AB, A) / p + dist(CD, D) / q + dist(AB, CD) / r);
}
R F(Point AB) {
R L = 0, R = 1;
int T = 200;
while(T --) {
double M1 = L + (R - L) / 3;
double M2 = R - (R - L) / 3;
Point P1 = D + M1 * D_DC;
Point P2 = D + M2 * D_DC;
double f1 = f(AB, P1), f2 = f(AB, P2);
if(f1 < f2) {
R = M2;
} else {
L = M1;
}
}
return f(AB, D + L * D_DC);
}
R solve() {
R L = 0, R = 1;
int T = 200;
while(T --) {
double M1 = L + (R - L) / 3;
double M2 = R - (R - L) / 3;
Point P1 = A + M1 * D_AB;
Point P2 = A + M2 * D_AB;
double F1 = F(P1), F2 = F(P2);
if(F1 < F2) {
R = M2;
} else {
L = M1;
}
}
return F(A + L * D_AB);
}
int main() {
scanf("%lf%lf%lf%lf", &A.x, &A.y, &B.x, &B.y);
scanf("%lf%lf%lf%lf", &C.x, &C.y, &D.x, &D.y);
scanf("%lf%lf%lf", &p, &q, &r);
D_AB = B - A; D_DC = C - D;
printf("%.2lf\n", solve());
return 0;
}