UVa 12524 Arranging Heaps

题目描述

一家矿业公司在长河上有 NNN 个采矿点,每个采矿点位于距离河源 XiX_iXi 的位置,并产出重量为 WiW_iWi 的矿堆。现在需要将这些矿堆重新组合成 KKK 个矿堆(K<NK < NK<N),每个新矿堆必须位于某个原始采矿点。

移动规则如下:

  • 驳船只能从上游向下游行驶
  • 将重量为 WWW 的矿堆从 XXX 移动到 YYYY>XY > XY>X)的成本为 W×(Y−X)W \times (Y - X)W×(YX)
  • 矿堆可以不被移动
  • 总成本是所有移动成本之和

求将 NNN 个初始矿堆重新组合成 KKK 个矿堆的最小总成本。

输入格式

  • 第一行:NNNKKK
  • 接下来 NNN 行:每行包含 XiX_iXiWiW_iWi
  • 采矿点按 XXX 严格升序给出

数据范围

  • 1≤K<N≤10001 \leq K < N \leq 10001K<N1000
  • 1≤Xi,Wi≤1061 \leq X_i, W_i \leq 10^61Xi,Wi106

题目分析

问题本质

这是一个典型的动态规划问题,需要将 NNN 个有序点分成 KKK 段,每段内的所有矿堆都移动到该段的最后一个采矿点,使得总移动成本最小。

关键观察

  1. 移动方向限制:由于只能向下游移动,最优策略一定是将区间 [l,r][l, r][l,r] 内的所有矿堆都移动到位置 xrx_rxr
  2. 成本计算:将区间 [l,r][l, r][l,r] 内矿堆移动到 xrx_rxr 的成本为:
    cost(l,r)=∑i=lrwi×(xr−xi)=xr×(sumWr−sumWl−1)−(sumXWr−sumXWl−1) \text{cost}(l, r) = \sum_{i=l}^{r} w_i \times (x_r - x_i) = x_r \times (\text{sumW}_r - \text{sumW}_{l-1}) - (\text{sumXW}_r - \text{sumXW}_{l-1}) cost(l,r)=i=lrwi×(xrxi)=xr×(sumWrsumWl1)(sumXWrsumXWl1)
    其中 sumW\text{sumW}sumW 是重量前缀和,sumXW\text{sumXW}sumXWwi×xiw_i \times x_iwi×xi 的前缀和

动态规划定义

dp[i][k]dp[i][k]dp[i][k] 表示将前 iii 个矿堆分成 kkk 段的最小成本。

初始状态:
dp[i][1]=cost(1,i)dp[i][1] = \text{cost}(1, i)dp[i][1]=cost(1,i)

状态转移:
dp[i][k]=min⁡j<i{dp[j][k−1]+cost(j+1,i)}dp[i][k] = \min_{j < i} \{ dp[j][k-1] + \text{cost}(j+1, i) \}dp[i][k]=j<imin{dp[j][k1]+cost(j+1,i)}

复杂度挑战

直接实现上述 DP\texttt{DP}DP 的时间复杂度为 O(N2K)O(N^2K)O(N2K),在 N,K≤1000N, K \leq 1000N,K1000 时会超时,需要优化。

斜率优化

优化思路

将转移方程展开:
dp[i][k]=xi×sumWi−sumXWi+min⁡j<i{−sumWj×xi+(dp[j][k−1]+sumXWj)} dp[i][k] = x_i \times \text{sumW}_i - \text{sumXW}_i + \min_{j < i} \{ -\text{sumW}_j \times x_i + (dp[j][k-1] + \text{sumXW}_j) \} dp[i][k]=xi×sumWisumXWi+j<imin{sumWj×xi+(dp[j][k1]+sumXWj)}

这可以看作对于每个 xix_ixi,在直线集合中寻找最小值:

  • 直线斜率:mj=−sumWjm_j = -\text{sumW}_jmj=sumWj
  • 直线截距:bj=dp[j][k−1]+sumXWjb_j = dp[j][k-1] + \text{sumXW}_jbj=dp[j][k1]+sumXWj
  • 查询点:x=xix = x_ix=xi

凸壳优化实现

使用单调队列维护下凸壳:

  1. 插入新直线时,检查队尾三条直线是否保持凸性
  2. 查询最小值时,检查队首两条直线的交点,淘汰不优的直线

算法步骤

  1. 预处理前缀和数组 sumWsumWsumWsumXWsumXWsumXW
  2. 初始化 dp[i][1]dp[i][1]dp[i][1]
  3. 对于 k=2k = 2k=2KKK
    • 清空单调队列
    • 对于 i=ki = ki=kNNN
      • 将直线 (−sumWi−1,dp[i−1][k−1]+sumXWi−1)(-sumW_{i-1}, dp[i-1][k-1] + sumXW_{i-1})(sumWi1,dp[i1][k1]+sumXWi1) 加入队列
      • 从队首查询在 xix_ixi 处的最小值
      • 更新 dp[i][k]dp[i][k]dp[i][k]

复杂度分析

  • 时间复杂度O(NK)O(NK)O(NK),每个矿堆在每个 kkk 值下最多被插入和弹出队列一次
  • 空间复杂度O(NK)O(NK)O(NK)

参考代码

// Arranging Heaps
// UVa ID: 12524
// Verdict: Accepted
// Submission Date: 2025-11-26
// UVa Run Time: 0.700s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const int MAXN = 1005;
const ll INF = 1e18;

ll x[MAXN], w[MAXN];
ll sumW[MAXN], sumXW[MAXN];
ll dp[MAXN][MAXN];

struct Line {
    ll m, b;
    ll eval(ll x) const { return m * x + b; }
};

bool bad(const Line& l1, const Line& l2, const Line& l3) {
    return (l3.b - l1.b) * (l1.m - l2.m) <= (l2.b - l1.b) * (l1.m - l3.m);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, k;
    while (cin >> n >> k) {
        for (int i = 1; i <= n; i++) {
            cin >> x[i] >> w[i];
            sumW[i] = sumW[i-1] + w[i];
            sumXW[i] = sumXW[i-1] + w[i] * x[i];
        }

        for (int i = 1; i <= n; i++) {
            dp[i][1] = x[i] * sumW[i] - sumXW[i];
            for (int j = 2; j <= k; j++)
                dp[i][j] = INF;
        }

        for (int j = 2; j <= k; j++) {
            deque<Line> dq;
            
            for (int i = j; i <= n; i++) {
                Line newLine = {-sumW[i-1], dp[i-1][j-1] + sumXW[i-1]};
                
                while (dq.size() >= 2 && bad(dq[dq.size()-2], dq.back(), newLine))
                    dq.pop_back();
                dq.push_back(newLine);
                
                while (dq.size() >= 2 && dq[0].eval(x[i]) >= dq[1].eval(x[i]))
                    dq.pop_front();
                
                dp[i][j] = dq.front().eval(x[i]) + (x[i] * sumW[i] - sumXW[i]);
            }
        }

        cout << dp[n][k] << "\n";
    }
    return 0;
}

总结

本题展示了动态规划与斜率优化的经典结合:

  1. 通过问题分析将原问题转化为区间划分问题
  2. 利用前缀和快速计算区间成本
  3. 通过斜率优化将平方复杂度降为线性复杂度

斜率优化的关键在于识别出转移方程中的线性结构,并通过维护凸壳来快速查询最小值。这种技巧在解决许多最优化问题中都非常有效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值