货币兑换
一.前言
这道题不难,但是要对知识点掌握的比较熟练,理解比较透彻(或者说这才是难点?)
二.题解
1.郭老师的决策点比较方法
郭老师讲的决策点方法通俗易懂,能及其节省脑细胞的解决许多斜率优化的题,但是会遇到负数的困扰,例如这道题,若用郭老师的办法,则会推出下列式子
状态转移方程
n u m [ j ] = d p [ j ] / ( a [ j ] . a ∗ a [ j ] . R a t e + a [ j ] . b ) ; num[j] = dp[j] / (a[j].a * a[j].Rate + a[j].b); num[j]=dp[j]/(a[j].a∗a[j].Rate+a[j].b);
d p [ i ] = M a x ( d p [ i ] , n u m [ j ] ∗ a [ j ] . R a t e ∗ a [ i ] . a + n u m [ j ] ∗ a [ i ] . b ) dp[i] = Max (dp[i], num[j] * a[j].Rate * a[i].a + num[j] * a[i].b) dp[i]=Max(dp[i],num[j]∗a[j].Rate∗a[i].a+num[j]∗a[i].b)
若 j < k j < k j<k w j < w k w_j < w_k wj<wk ( w j w_j wj表示选择 j j j决策点的效益)
则 n u m [ j ] ∗ a [ j ] . R a t e ∗ a [ i ] . a + n u m [ j ] ∗ a [ i ] . b num[j] * a[j].Rate * a[i].a + num[j] * a[i].b num[j]∗a[j].Rate∗a[i].a+num[j]∗a[i].b < < < n u m [ k ] ∗ a [ k ] . R a t e ∗ a [ i ] . a + n u m [ k ] ∗ a [ i ] . b num[k] * a[k].Rate * a[i].a + num[k] * a[i].b num[k]∗a[k].Rate∗a[i].a+num[k]∗a[i].b
a [ i ] . a ∗ ( n u m [ j ] ∗ a [ j ] . R a t e − n u m [ k ] ∗ a [ k ] . R a t e ) < a [ i ] . b ∗ ( n u m [ k ] − n u m [ j ] ) a[i].a * (num[j] * a[j].Rate - num[k] * a[k].Rate) < a[i].b * (num[k] - num[j]) a[i].a∗(num[j]∗a[j].Rate−num[k]∗a[k].Rate)<a[i].b∗(num[k]−num[j])
若 n u m [ j ] ∗ a [ j ] . R a t e − n u m [ k ] ∗ a [ k ] . R a t e > 0 num[j] * a[j].Rate - num[k] * a[k].Rate > 0 num[j]∗a[j].Rate−num[k]∗a[k].Rate>0 则 a [ i ] . a a [ i ] . b < n u m [ k ] − n u m [ j ] n u m [ j ] ∗ a [ j ] . R a t e − n u m [ k ] ∗ a [ k ] . R a t e \frac{a[i].a}{a[i].b}<\frac{num[k]-num[j]}{num[j]*a[j].Rate-num[k]*a[k].Rate} a[i].ba[i].a<num[j]∗a[j].Rate−num[k]∗a[k].Ratenum[k]−num[j]
若 n u m [ j ] ∗ a [ j ] . R a t e − n u m [ k ] ∗ a [ k ] . R a t e < 0 num[j] * a[j].Rate - num[k] * a[k].Rate < 0 num[j]∗a[j].Rate−num[k]∗a[k].Rate<0 则 a [ i ] . a a [ i ] . b > n u m [ k ] − n u m [ j ] n u m [ j ] ∗ a [ j ] . R a t e − n u m [ k ] ∗ a [ k ] . R a t e \frac{a[i].a}{a[i].b}>\frac{num[k]-num[j]}{num[j]*a[j].Rate-num[k]*a[k].Rate} a[i].ba[i].a>num[j]∗a[j].Rate−num[k]∗a[k].Ratenum[k]−num[j]
若 n u m [ j ] ∗ a [ j ] . R a t e − n u m [ k ] ∗ a [ k ] . R a t e < 0 num[j] * a[j].Rate - num[k] * a[k].Rate < 0 num[j]∗a[j].Rate−num[k]∗a[k].Rate<0 则 n u m [ j ] < n u m [ k ] num[j] < num[k] num[j]<num[k]
三个分类讨论,怎么去保存斜率?所以这个方法不可取了(但大部分题这种方法简单得多)
2.一次函数法
d p [ i ] = n u m [ j ] ∗ a [ j ] . R a t e ∗ a [ i ] . a + n u m [ j ] ∗ a [ i ] . b dp[i] = num[j] * a[j].Rate * a[i].a + num[j] * a[i].b dp[i]=num[j]∗a[j].Rate∗a[i].a+num[j]∗a[i].b
n u m [ j ] ∗ a [ i ] . b = d p [ i ] − n u m [ j ] ∗ a [ j ] . R a t e ∗ a [ i ] . a num[j] * a[i].b = dp[i] -num[j] * a[j].Rate * a[i].a num[j]∗a[i].b=dp[i]−num[j]∗a[j].Rate∗a[i].a
n u m [ j ] = d p [ i ] − n u m [ j ] ∗ a [ j ] . R a t e ∗ a [ i ] . a a [ i ] . b num[j] = \frac{dp[i] - num[j] * a[j].Rate * a[i].a}{a[i].b} num[j]=a[i].bdp[i]−num[j]∗a[j].Rate∗a[i].a
n u m [ j ] = − a [ i ] . a a [ i ] . b ∗ n u m [ j ] ∗ a [ j ] . R a t e + d p [ i ] a [ i ] . b num[j] = -\frac{a[i].a}{a[i].b} * num[j]*a[j].Rate + \frac{dp[i]}{a[i].b} num[j]=−a[i].ba[i].a∗num[j]∗a[j].Rate+a[i].bdp[i]
令 y = n u m [ j ] y = num[j] y=num[j] k = − a [ i ] . a a [ i ] . b k = -\frac{a[i].a}{a[i].b} k=−a[i].ba[i].a x = n u m [ j ] ∗ a [ j ] . R a t e x = num[j]*a[j].Rate x=num[j]∗a[j].Rate b = d p [ i ] a [ i ] . b b = \frac{dp[i]}{a[i].b} b=a[i].bdp[i]
则 y = k x + b y = kx + b y=kx+b, 老一次函数了
维护一个斜率递增的凸包即可
3.小结
(1).
决策点比较法无法处理:维护存在两对相邻点斜率乘积
≤
\leq
≤
0
0
0的情况。
即如下图
(2).
有些时候我们的
x
x
x 并不是单调递增,所以不能用队列来维护,只能用
C
D
Q
CDQ
CDQ 或者其他我不会更高级的算法去维护。本题中用
C
D
Q
CDQ
CDQ 只需要将
x
x
x 按从小到大排序即可。
(这就是我为什么一直 WA
0
0
0)
4.代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
template <typename T>
void read (T &x) {
x = 0; T f = 1;
char ch = getchar ();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar ();
}
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + ch - '0';
ch = getchar ();
}
x *+ f;
}
template <typename T>
T Max (T x, T y) { return x > y ? x : y; }
template <typename T>
T Min (T x, T y) { return x < y ? x : y; }
template <typename T>
T Abs (T x) { return x > 0 ? x : -x; }
const int Maxn = 1e5;
const double Inf = 0x7f7f7f7f7f7f;
int n, s;
double dp[Maxn + 5];
struct Date {
double a, b, Rate;
}a[Maxn + 5];
int hh, tt;
int que[Maxn + 5];
double num (int j) { return dp[j] / (a[j].a * a[j].Rate + a[j].b); }
double Get_x (int j) { return num (j) * a[j].Rate; }
double Get_y (int j) { return num (j); }
double Get_dp (int i, int j) { return num (j) * a[j].Rate * a[i].a + num (j) * a[i].b; }
double Get_k (int i, int j) { return (Get_y (j) - Get_y (i)) / (Get_x (j) - Get_x (i)); }
void add (int x) {
while (hh < tt && (Get_y (que[tt]) - Get_y (que[tt - 1])) * (Get_x (x) - Get_x (que[tt - 1])) <= (Get_y (x) - Get_y (que[tt - 1])) * (Get_x (que[tt]) - Get_x (que[tt - 1]))) {
tt--;
}
que[++tt] = x;
}
int q[Maxn + 5];
bool cmpx (int x, int y) {
return Get_x (x) < Get_x (y);
}
void CDQ (int l, int r) {
if (l == r) return;
int mid = (l + r) >> 1;
CDQ (l, mid);
sort (q + l, q + 1 + mid, cmpx);
hh = 1; tt = 0; double _max = -Inf;
for (int i = l; i <= mid; i++) {
_max = Max (_max, dp[q[i]]);
add (q[i]);
}
for (int i = mid + 1; i <= r; i++) {
int L = hh, R = tt;
while (L + 1 < R) {
int mid = (L + R) >> 1;
if (Get_dp (q[i], que[mid]) < Get_dp (q[i], que[mid + 1])) L = mid;
else R = mid;
}
dp[q[i]] = Max (dp[q[i]], Get_dp (q[i], que[L]));
dp[q[i]] = Max (dp[q[i]], Get_dp (q[i], que[R]));
dp[q[i]] = Max (dp[q[i]], _max);
}
sort (q + l, q + 1 + r);
CDQ (mid + 1, r);
}
int main () {
read (n); read (s);
for (int i = 1; i <= n; i++) {
scanf ("%lf %lf %lf", &a[i].a, &a[i].b, &a[i].Rate);
}
dp[1] = s;
for (int i = 1; i <= n; i++) q[i] = i;
CDQ (1, n);
double ans = s;
for (int i = 1; i <= n; i++) ans = Max (ans, dp[i]);
printf ("%.3lf", ans);
return 0;
}
5.后记
这道题真的深深地打醒了我,我不得不重新审视了一下之前斜率优化中我囫囵吞枣的记类型过去的重点,当知识点出现较大变化时,便束手无措了。这道题是一个很好的教训。