洛谷P2900 [USACO08MAR] Land Acquisition G 题解 斜率优化DP
题目链接:https://www.luogu.com.cn/problem/P2900
预处理
若一块土地的 长和宽都不超过 另一块土地,则它可以并入那一组而不增加费用,因此可直接丢弃。
具体做法:
- 按 \(w\) 升序排序,若 \(w\) 相同则按 \(h\) 升序。
- 从后往前扫描,维护一个后缀最大高度 \(H\):
- 若当前土地的高度 \(h \le H\),说明它被后面的某块土地支配,可以丢弃。
- 否则保留,并更新 \(H = h\)。
这样处理后的土地序列满足:
\[w_1 \lt w_2 \lt \cdots \lt w_n,\quad h_1 \gt h_2 \gt \cdots \gt h_n
\]
DP 转移
设 \(f[i]\) 表示购买前 \(i\) 块土地的最小费用。
枚举最后一组的起点 \(j+1\),该组土地为 \([j+1, i]\)。由于 \(w\) 递增、\(h\) 递减,该组的最大长是 \(w_i\),最大宽是 \(h_{j+1}\),所以该组花费为 \(h_{j+1} \cdot w_i\)。
转移方程:
\[f[i] = \min_{0 \le j < i} \left\{ f[j] + h_{j+1} \cdot w_i \right\}
\]
斜率优化
令
\[X(j) = h_{j+1},\quad Y(j) = f[j]
\]
则转移变为:
\[f[i] = \min_{j < i} \left\{ Y(j) + w_i \cdot X(j) \right\}
\]
可以看成用一条斜率为 \(-w_i\) 的直线去切由点 \((X(j),Y(j))\) 构成的凸包,切点即为最优转移。
由于在预处理后 \(w_i\) 单调递增,\(X(j)=h_{j+1}\) 单调递减,因此斜率 \(-w_i\) 单调递减,可以使用单调队列维护凸包,均摊 \(O(1)\) 转移。
示例程序:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 5e4 + 5;
int n, que[maxn], l, r;
struct Node {
int w, h;
} a[maxn];
bool vis[maxn];
ll f[maxn];
ll X(int i) { return a[i+1].h; }
ll Y(int i) { return f[i]; }
ll fz(int a, int b) { return Y(b) - Y(a); }
ll fm(int a, int b) { return X(b) - X(a); }
void init() {
sort(a+1, a+n+1, [](auto a, auto b) {
return a.w < b.w || a.w == b.w && a.h < b.h;
});
for (int i = n-1, H = a[n].h; i >= 1; i--) {
if (a[i].h <= H)
vis[i] = true;
else
H = a[i].h;
}
int m = 0;
for (int i = 1; i <= n; i++)
if (!vis[i])
a[++m] = a[i];
n = m;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d%d", &a[i].h, &a[i].w);
init();
for (int i = 1; i <= n; i++) {
while (l < r && fz(que[l+1], que[l]) >= -a[i].w * fm(que[l+1], que[l]))
l++;
int j = que[l];
f[i] = f[j] + 1ll * a[j+1].h * a[i].w;
if (i == n)
break;
while (l < r && fz(i, que[r]) * fm(que[r], que[r-1]) >= fz(que[r], que[r-1]) * fm(i, que[r]))
r--;
que[++r] = i;
}
printf("%lld\n", f[n]);
return 0;
}
浙公网安备 33010602011771号