题目
思路
都是利用「买则疯狂买,卖则疯狂卖」进行 d p \tt dp dp 的。而这种性质也很容易证明:如果第 i i i 天买入、第 j j j 天卖出是有利可图的,那么增加本金只会赚更多。
回到题目上。用
f
(
i
)
f(i)
f(i) 表示第
i
i
i 天卖光金券后可以获得最多的钱。考虑一下第
j
j
j 天买入、第
i
i
i 天卖出,能赚多少。买入的数量是
A
A
A 券
r
j
a
j
r
j
+
b
j
⋅
m
o
n
e
y
{r_j\over a_jr_j+b_j}\cdot money
ajrj+bjrj⋅money 与
B
B
B 券
1
a
j
r
j
+
b
j
⋅
m
o
n
e
y
{1\over a_jr_j+b_j}\cdot money
ajrj+bj1⋅money ,所以转移式
f
(
i
)
=
max
j
=
1
i
−
1
[
a
i
r
j
+
b
i
a
j
r
j
+
b
j
⋅
f
(
j
)
]
f(i)=\max_{j=1}^{i-1}\left[{a_ir_j+b_i\over a_jr_j+b_j}\cdot f(j)\right]
f(i)=j=1maxi−1[ajrj+bjairj+bi⋅f(j)]
记
v
j
=
f
(
j
)
a
j
r
j
+
b
j
,
p
j
=
r
j
v
j
v_j={f(j)\over a_jr_j+b_j},\;p_j=r_jv_j
vj=ajrj+bjf(j),pj=rjvj 则
f
(
i
)
=
b
i
⋅
max
j
=
1
i
−
1
(
p
j
⋅
a
i
b
i
+
v
j
)
f(i)=b_i\cdot\max_{j=1}^{i-1}\left(p_j\cdot {a_i\over b_i}+v_j\right)
f(i)=bi⋅j=1maxi−1(pj⋅biai+vj)
这就是一个李超树板题了嘛。复杂度 O ( n log n ) \mathcal O(n\log n) O(nlogn) 。
代码
一开始我傻了,直接用 d o u b l e \tt double double,结果精度爆炸。事实上,查询点 a i b i {a_i\over b_i} biai 是固定的,所以可以离散。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long int_;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline void writeint(int_ x){
if(x > 9) writeint(x/10);
putchar((x-x/10*10)^48);
}
struct Line{
double k, b; int id;
Line(){ k = b = 0; }
double operator()(const double &x) const {
return k*x+b; // definition
}
};
bool low(const Line &A,const Line &B,const double &x){
return (A.k-B.k)*x < B.b-A.b;
}
const int MaxN = 500005;
int son[MaxN][2], cntNode;
Line line[MaxN]; double all[MaxN];
void modify(Line x,int &o,int l,int r){
if(!o) o = ++ cntNode; // new node
int mid = (l+r)>>1; // all[mid]
if(low(line[o],x,all[l])) swap(x,line[o]);
if(low(x,line[o],all[r])) return ;
if(low(x,line[o],all[mid]))
modify(x,son[o][1],mid+1,r);
else{
modify(line[o],son[o][0],l,mid);
line[o] = x; // a better ans
}
}
double dp[MaxN], a[MaxN], b[MaxN], R[MaxN];
double query(int qid,int real,int o,int l,int r){
double res = 0;
for(int d; o; o=son[o][d]){
res = max(res,
(a[real]*R[line[o].id]
+b[real])*dp[line[o].id]
/(a[line[o].id]*R[line[o].id]
+b[line[o].id])
);
if((d = (qid*2 > l+r)))
l = (l+r)/2+1;
else r = (l+r)>>1;
}
return res;
}
int main(){
int n = readint(); dp[0] = readint();
for(int i=1; i<=n; ++i){
scanf("%lf %lf %lf",&a[i],&b[i],&R[i]);
all[i] = a[i]/b[i]; // all needed X
}
sort(all+1,all+n+1); Line now;
int tot = unique(all+1,all+n+1)-all;
for(int i=1,rt=0; i<=n; ++i){
int haxi = lower_bound(all+1,all+tot,a[i]/b[i])-all;
dp[i] = max(dp[i-1],query(haxi,i,rt,1,tot-1));
now.b = dp[i]/(a[i]*R[i]+b[i]); // v_j
now.k = now.b*R[i], now.id = i;
modify(now,rt,1,tot-1); // add i
}
printf("%.3f\n",dp[n]);
return 0;
}