【题解】P4027 [NOI2007] 货币兑换
题意好长,但是不想概括了。
\cdq/!
思路
cdq 分治 + 凸包。
首先考虑令 \(f_i\) 表示第 \(i\) 天可以得到的最大金额,\(f_1 = s\)
因为 \(rate_i\) 是常数,并且保证存在最优方案使得每次买进卖出都是全部金额,所以最优方案下第 \(i\) 天的 A 券和 B 券的比例是固定的,令第 \(i\) 天的 A 券有 \(x_i\) 个,B 券有 \(y_i\) 个
可以列方程组:
\[\begin{cases}
\frac{x_i}{y_i} = rate_i \\
x_i A_i + y_i B_i = f_i
\end{cases}
\]
解得
\[\begin{cases}
x_i = \frac{f_i \cdot rate_i}{a_i + rate_i b_i} \\
y_i = \frac{f_i}{a_i + rate_i b_i}
\end{cases}
\]
第 \(i\) 天卖出它们可以得到的金额是 \(x_i A_i + y_i B_i\),把 \(B_i\) 提出来:
\(B_i (\frac{A_i}{B_i} x_i + y_i)\)
括号里面是一个一次函数的形式,\(x_i\) 是斜率,\(y_i\) 是截距。
所以现在的问题变成了:
-
插入一条直线
-
求最高点
好家伙,没啥单调的东西,有人整了用 splay 维护的 \(O(n \log n)\) 好活,但我看不懂。
于是考虑用 cdq 分治进行动态转静态。
静态的情况下可以直接把所有转移点按照斜率排序构造凸包。
查询前先按 \(x\) 排序,又是可以指针维护的东西。
注意到转移对下标的前后顺序也有要求,这里可以先在 cdq 处理当前层的时候分拣出下标在中点两侧的元素,最后再归并起来,复杂度就优化了。
时间复杂度 \(O(n \log n)\),wonderful!
代码
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 5;
struct line
{
double k, b, x;
} s[maxn], q[maxn], l;
struct item
{
int p;
double x;
} c[maxn], sr[maxn];
double interx(line& a, line& b) { return (a.k == b.k ? (a.b < b.b ? 1e12 : -1e12) : (b.b - a.b) / (a.k - b.k));}
int n, top;
double a[maxn], b[maxn], rate[maxn], f[maxn];
void solve(int l, int r)
{
if (l == r)
{
f[l] = max(f[l], f[l - 1]);
s[l].b = f[l] / (b[l] + rate[l] * a[l]);
s[l].k = s[l].b * rate[l];
return;
}
int mid = (l + r) >> 1, tl = l - 1, tr = 0;
for (int i = l; i <= r; i++)
if (c[i].p <= mid) c[++tl] = c[i];
else sr[++tr] = c[i];
for (int i = mid + 1; i <= r; i++) c[i] = sr[i - mid];
solve(l, mid);
top = 0;
for (int i = l; i <= mid; i++)
{
while ((top > 1) && interx(s[i], q[top]) < q[top].x) top--;
q[++top] = s[i];
q[top].x = interx(s[i], q[top - 1]);
}
for (int i = mid + 1, p = 1; i <= r; i++)
{
while ((p < top) && (q[p + 1].x < c[i].x)) p++;
int pos = c[i].p;
f[pos] = max(f[pos], a[pos] * q[p].k + b[pos] * q[p].b);
}
solve(mid + 1, r);
tr = mid + 1;
top = 0;
for (int i = l; i <= mid; i++)
{
while ((tr <= r) && (s[tr].k < s[i].k)) q[++top] = s[tr++];
q[++top] = s[i];
}
while (tr <= r) q[++top] = s[tr++];
for (int i = l; i <= r; i++) s[i] = q[i - l + 1];
}
bool cmp(const item& a, const item& b) { return (a.x < b.x); }
int main()
{
// freopen("P4027_1.in", "r", stdin);
scanf("%d%lf", &n, &f[1]);
for (int i = 1; i <= n; i++)
{
scanf("%lf%lf%lf", &a[i], &b[i], &rate[i]);
c[i] = (item){i, a[i] / b[i]};
}
sort(c + 1, c + n + 1, cmp);
solve(1, n);
printf("%.3lf\n", f[n]);
return 0;
}