[NOI2007]货币兑换
设\(dp[i],a[i],b[i],x[i],y[i]\)为第\(i\)天时的最大收益/A卷价格/B卷价格/将所有现金兑成A卷数量/B卷
于是有
\[dp[i]=\max(dp[i-1],x[j]*a[i]+y[j]*b[i])
\]
然后可以写成斜率优化形式
\[y[j]=-\frac{a[i]}{b[i]}x[j]+\frac{dp[i]}{b[i]}
\]
对于每个点\((x[j],y[j])\),我们用一根斜率为\(-\frac{a[i]}{b[i]}\)的直线去经过它,使截距最大,也就是维护上凸包
因为每一点x坐标,斜率都不单调,所以上平衡树 cdq
先把所有点按k递增顺序排好,再按天数分治,使得处理时是按天数递增的顺序
因为这是dp,所以要先处理左区间,再递归右区间
在处理影响时我们不用管左区间的顺序,也就是可以按x坐标递增插入凸包里
递归出口时首先\(dp[i]=\max(dp[i],dp[i-1])\),然后再计算\(y[i]=\frac{dp[i]}{a[i]*rate[i]+b[i]},x[i]=y[i]*a[i]\)
加上归并把复杂度降低到\(O(n\log n)\)
注意这道题似乎有点卡精度(虽然我没有)
#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
#define db double
#define eps 1e-9
#define inf 1e18
db dp[maxn];
int n, sta[maxn];
struct Cash
{
db a, b, rate, x, y, k;
int id;
bool operator < (const Cash& p) const { return k < p.k; }
friend void read(Cash& p) { scanf("%lf%lf%lf", &p.a, &p.b, &p.rate); }
}cash[maxn], tp[maxn];
db getk(int a, int b)
{
if (fabs(cash[a].x - cash[b].x) <= eps) return inf;
return (cash[b].y - cash[a].y) / (cash[b].x - cash[a].x);
}
void merge_sort(int l, int r, int mid)
{
int t1 = l, t2 = mid + 1, k = l;
while (t1 <= mid && t2 <= r)
{
if (cash[t1].x < cash[t2].x) tp[k++] = cash[t1++];
else tp[k++] = cash[t2++];
}
while (t1 <= mid) tp[k++] = cash[t1++];
while (t2 <= r) tp[k++] = cash[t2++];
for (int i = l; i <= r; ++i) cash[i] = tp[i];
}
void cdq(int l, int r)
{
if (l == r)
{
dp[l] = max(dp[l], dp[l - 1]);
cash[l].y = dp[l] / (cash[l].a * cash[l].rate + cash[l].b), cash[l].x = cash[l].y * cash[l].rate;
return;
}
int mid = (l + r) >> 1, top = 0;
for (int i = l, t1 = l, t2 = mid + 1; i <= r; ++i)//先按天数排序,递归
{
if (cash[i].id <= mid) tp[t1++] = cash[i];
else tp[t2++] = cash[i];
}
for (int i = l; i <= r; ++i) cash[i] = tp[i];
cdq(l, mid);
for (int i = l; i <= mid; ++i)//维护左区间的上凸包
{
while (top >= 2 && getk(sta[top], i) > getk(sta[top - 1], sta[top])) --top;
sta[++top] = i;
}
for (int i = mid + 1; i <= r; ++i)//更新右区间答案
{
while (top >= 2 && getk(sta[top - 1], sta[top]) <= cash[i].k) --top;
dp[cash[i].id] = max(dp[cash[i].id], cash[sta[top]].x * cash[i].a + cash[sta[top]].y * cash[i].b);
}
cdq(mid + 1, r);
merge_sort(l, r, mid);
}
int main()
{
scanf("%d%lf", &n, &dp[0]);
for (int i = 1; i <= n; ++i) read(cash[i]), cash[i].k = -cash[i].a / cash[i].b, cash[i].id = i;
sort(cash + 1, cash + n + 1); cdq(1, n);
printf("%.3lf", dp[n]);
return 0;
}
一切伟大的行动和思想,都有一个微不足道的开始。
There is a negligible beginning in all great action and thought.
There is a negligible beginning in all great action and thought.