[USACO 08MAR]土地购买
Description
题库链接
给你 \(n\) 块不同大小的土地。你可分批购买这些土地,每一批价格为这一批中最大的长乘最大的宽。问你买下所有土地的花费最小为多少。
\(1\leq n\leq 50000\)
Solution
有一个贪心的思想就是一块土地如果是 \(w_i\times h_i\),若 \(\exists j\) 满足 \(w_i\leq w_j,h_i\leq h_j\)。那么 \(i\) 这块土地一定不会参与到花费计算的。这是因为 \(i\) 这块土地可以放在 \(j\) 那一批,然后 \(i\) 的长和宽一定不是最大的。
因此我们把这些土地按 \(w\) 从小到大排序,那么 \(h\) 一定是单调递减的。基于此,我们还可以得出另外一个结论,同一批次在这个序列中一定是连续的一段。这是因为这一段的 \(w,h\) 最大值一定是在端点取,你把中间某个地不放在这一段是对这一段的花费不产生影响的,因此不如选上这一个点。
基于上述两个结论,我们可以对排完序的序列进行 DP。记 \(f_i\) 表示购买 \(1\sim i\) 的土地的最小的花费,那么 \(f_i=\min\{f_j+w_i\times h_j\}\)。
由于 \(w_i\) 和 \(h_j\) 单调性相反,因此可以用单调队列维护上凸包斜率优化解决。
Code
#include
using namespace std;
const int N = 50000+5;
int n, tot, q[N], head, tail;
struct tt {
int w, h;
bool operator < (const tt &b) const {
return w == b.w ? h < b.h : w < b.w;
}
} a[N], b[N];
long long f[N];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d%d", &a[i].w, &a[i].h);
sort(a+1, a+n+1);
for (int i = 1; i <= n; i++) {
while (tot && b[tot].h <= a[i].h) --tot;
b[++tot] = a[i];
}
n = tot; tail = -1;
for (int i = 1; i <= n; i++) {
while (head < tail && (-f[i-1]+f[q[tail]-1])*(b[i].h-b[q[tail-1]].h)
<= (-f[i-1]+f[q[tail-1]-1])*(b[i].h-b[q[tail]].h)) --tail;
q[++tail] = i;
while (head < tail && -f[q[head]-1]+f[q[head+1]-1] <= 1ll*b[i].w*(b[q[head]].h-b[q[head+1]].h)) ++head;
f[i] = f[q[head]-1]+1ll*b[q[head]].h*b[i].w;
}
printf("%lld\n", f[n]);
return 0;
}