洛谷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;
}
posted @ 2026-09-17 19:34  quanjun  阅读(9)  评论(0)    收藏  举报