斜率优化动态规划

斜率优化动态规划

例题:

[ZJOI2007] 仓库建设

P3195 [HNOI2008] 玩具装箱

题目描述

P 教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京。他使用自己的压缩器进行压缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中。

P 教授有编号为 \(1 \cdots n\)\(n\) 件玩具,第 \(i\) 件玩具经过压缩后的一维长度为 \(C_i\)

为了方便整理,P 教授要求:

  • 在一个一维容器中的玩具编号是连续的。
  • 同时如果一个一维容器中有多个玩具,那么两件玩具之间要加入一个单位长度的填充物。形式地说,如果将第 \(i\) 件玩具到第 \(j\) 个玩具放到一个容器中,那么容器的长度将为 \(x=j-i+\sum\limits_{k=i}^{j}C_k\)

制作容器的费用与容器的长度有关,根据教授研究,如果容器长度为 \(x\),其制作费用为 \((x-L)^2\)。其中 \(L\) 是一个常量。P 教授不关心容器的数目,他可以制作出任意长度的容器,甚至超过 \(L\)。但他希望所有容器的总费用最小。

输入格式

第一行有两个整数,用一个空格隔开,分别代表 \(n\)\(L\)

\(2\) 到 第 \((n + 1)\) 行,每行一个整数,第 \((i + 1)\) 行的整数代表第 \(i\) 件玩具的长度 \(C_i\)

输出格式

输出一行一个整数,代表所有容器的总费用最小是多少。

输入输出样例 #1

输入 #1

5 4
3
4
2
1
4

输出 #1

1

说明/提示

对于全部的测试点,\(1 \leq n \leq 5 \times 10^4\)\(1 \leq L \leq 10^7\)\(1 \leq C_i \leq 10^7\)

分析

显然可以用动态规划做,状态转移方程如下:

\[f_i=min\{f_j+((j-i-1)+\sum^{i}_{k=j+1}C_k-L)^2\} \]

\[S_i=\sum^{i}_{k=1}C_k+i \]

利用前缀和优化后:

\[f_i=min\{f_j+(S_i-S_j-(L+1))^2\}\quad(j\in[0,i-1]) \]

为了方便,提前将 \(L+1\),则:

\[f_i=min\{f_j+(S_i-S_j-L)^2\}\quad(j\in[0,i-1]) \]

显然,这样的算法时间复杂度为 \(O(n^2)\)(枚举 \(i\) 的同时向前枚举 \(j\))。

将右式展开并将只含 \(i\) 的项含有 \(j\) 的项分开,可得:

\[(S_i^2-2S_iL+L^2)+(f_j+S_j^2+2S_jL-2S_iS_j)=(S_i-L)^2+(f_j+S_j^2+2S_jL-2S_iS_j) \]

显然 \((S_i-L)^2\)常数\((f_j+S_j^2+2S_jL-2S_iS_j)\) \(j\) 变化

\(F_j=(f_j+S_j^2+2S_jL-2S_iS_j)\)

现在的目的是选取一个 \(min\{F_j\}\)

设决策 \(j_1\)\(j_2\) 满足 \(j_1<j_2\)\(F_{j_2}\le F_{j_1}\),即满足决策 \(j_2\) 更优

所以:

\[f_{j_2}+S_{j_2}^2+2S_{j_2}L-2S_iS_{j_2}\le f_{j_1}+S_{j_1}^2+2S_{j_1}L-2S_iS_{j_1} \]

将含有 \(S_i\) 的项移动至左侧,其余移动到右侧可得:

\[-2S_i(S_{j_2}-S_{j_1})\le (f_{j_1}+S_{j_1}^2+2S_{j_1}L)-(f_{j_2}+S_{j_2}^2+2S_{j_2}L) \]

将所有含 \(j\) 的项移动到右式并去除左式负号可得:

\[2S_i\ge \cfrac{(f_{j_2}+S_{j_2}^2+2S_{j_2}L)-(f_{j_1}+S_{j_1}^2+2S_{j_1}L)}{S_{j_2}-S_{j_1}} \]

可以观察到,若令 \(Y_j=f_j+S_j^2+2S_jL\)\(X_j = S_j\),可已发现右式变成了形式如下的斜率式

\[2S_i\ge \cfrac{Y_{j_2}-Y_{j_1}}{X_{j_2}-X_{j_1}}=k \]

为了方便,先令 \(k_0=2S_i\)

设有三个决策点 \(j_1\)\(j_2\)\(j_3\)\(j_1\)\(j_2\)连线斜率为 \(k_1\)\(j_2\)\(j_3\)连线斜率为 \(k_2\)

那么如果 \(j_2\) 为最优策略,那么一定满足:

\[\begin{cases} k_1\le k_0 \\\\ k_2>k_0 \end{cases} \]

此时形成了一个类似下凸包的结构

单调队列优化:

单调性分析:

  1. 如果出现上凸包,则凸包顶点一定不优,可删去。所以 \(k\) 单调递增
  2. 如果在当前边集中存在 \(k\le k_0\) 的线段,可删去,因为 \(k_0\) 单增(\(S_i\)单增),前面不优解在后面依然不优,可删去。

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 5e4 + 5;
struct node {
  double x,y;
  node() {}
  node(double _x,double _y) : x(_x),y(_y) {}
};
int n;
ll L;
ll C[maxn + 5],sum[maxn + 5],S[maxn + 5];
ll dp[maxn + 5];
int q[2 * maxn + 5]; // 存最优决策点的pos
int head = 1,tail = 0;
// 计算决策点
double X(int pos) { return (double)S[pos]; }
double Y(int pos) { return (double)(S[pos] * S[pos] + 2ll * S[pos] * L + dp[pos]); }
// 计算斜率
double slope(int pos1,int pos2) { return (Y(pos2) - Y(pos1)) / (X(pos2) - X(pos1)); }

int main() {
  scanf("%d %lld",&n,&L); L ++;
  for (int i = 1;i <= n;i ++) {
    scanf("%lld",&C[i]);
    sum[i] = sum[i - 1] + C[i];
    S[i] = sum[i] + i; // 预处理前缀优化
  }
  q[++ tail] = 0; // 重点!!! 初始决策可能是dp[0]
  for (int i = 1;i <= n;i ++) {
    double k0 = (double)(2 * S[i]);
    // head < tail ==> 保证至少有两个决策点(两点成一条直线ヾ(•ω•`)o)
    while (head < tail && slope(q[head],q[head + 1]) <= k0) head ++; // 去除<=k0的线段(左端点)
    int j = q[head]; // 开头取得当前最优决策点
    dp[i] = dp[j] + (S[i] - S[j] - L) * (S[i] - S[j] - L); // 正常计算答案
    while (head < tail && slope(q[tail - 1],q[tail]) >= slope(q[tail],i)) tail --; // 保证k单调递增
    q[++ tail] = i; // 新决策点
  }
  printf("%lld",dp[n]); // 答案取dp[n]
  return 0;
}
posted @ 2026-08-26 01:06  Denia-kawaii  阅读(2)  评论(0)    收藏  举报