斜率优化动态规划
斜率优化动态规划
例题:
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\)。
分析
显然可以用动态规划做,状态转移方程如下:
令
利用前缀和优化后:
为了方便,提前将 \(L+1\),则:
显然,这样的算法时间复杂度为 \(O(n^2)\)(枚举 \(i\) 的同时向前枚举 \(j\))。
将右式展开并将只含 \(i\) 的项与含有 \(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\) 更优
所以:
将含有 \(S_i\) 的项移动至左侧,其余移动到右侧可得:
将所有含 \(j\) 的项移动到右式并去除左式负号可得:
可以观察到,若令 \(Y_j=f_j+S_j^2+2S_jL\),\(X_j = S_j\),可已发现右式变成了形式如下的斜率式:
为了方便,先令 \(k_0=2S_i\)
设有三个决策点 \(j_1\),\(j_2\),\(j_3\),\(j_1\)与\(j_2\)连线斜率为 \(k_1\),\(j_2\)与\(j_3\)连线斜率为 \(k_2\)
那么如果 \(j_2\) 为最优策略,那么一定满足:
此时形成了一个类似下凸包的结构
单调队列优化:
单调性分析:
- 如果出现上凸包,则凸包顶点一定不优,可删去。所以 \(k\) 单调递增
- 如果在当前边集中存在 \(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;
}

浙公网安备 33010602011771号