[APIO2014]序列分割 题解
题目地址:ė …
May all the beauty be blessed.
题目地址:洛谷:【P3195】[HNOI2008]玩具装箱TOY – 洛谷、BZOJ:问题 1010. — [HNOI2008]玩具装箱toy
P教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京。他使用自己的压缩器进行压缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中。P教授有编号为1…N的N件玩具,第i件玩具经过压缩后变成一维长度为Ci.为了方便整理,P教授要求在一个一维容器中的玩具编号是连续的。同时如果一个一维容器中有多个玩具,那么两件玩具之间要加入一个单位长度的填充物,形式地说如果将第i件玩具到第j个玩具放到一个容器中,那么容器的长度将为 x=j-i+Sigma(Ck) i<=K<=j 制作容器的费用与容器的长度有关,根据教授研究,如果容器长度为x,其制作费用为(X-L)^2.其中L是一个常量。P教授不关心容器的数目,他可以制作出任意长度的容器,甚至超过L。但他希望费用最小.
输入格式:
第一行输入两个整数N,L.接下来N行输入Ci.1<=N<=50000,1<=L,Ci<=10^7
输出格式:
输出最小费用
输入样例#1:
5 4 3 4 2 1 4
输出样例#1:
1
斜率优化是一类利用决策单调性来优化DP转移的优化方式。因为其原理中一部分形似斜率,故名为斜率优化。
注意:以下推导为斜率优化的感性证明,严格的数学证明请参见其他资料。
首先我们很容易能想到的DP转移方程如下
dp[i] = \min\{ dp[j] + (sum[i] - sum[j] + i - j - 1 - L)^2 \}
我们令f[i] = sum[i] - i, C = L + 1,有
dp[i] = \min\{ dp[j] + (f[i] - f[j] - C)^2 \}
如果j是一个决策点,我们去掉min标记,展开平方,得到下式
dp[i] = dp[j] + f[i]^2 - 2f[i](f[j] + C) + (f[j] + C)^2
将与i无关的项放在等号右侧,而其他移至等号左侧
dp[i] + 2f[i](f[j] + C) - f[i]^2 = dp[j] + (f[j] + C)^2
在这个式子里,我们可以把2f[i]看成直线的斜率,那么就是一个斜率为2f[i]且过点 (f[j] + C, dp[j] + (f[j] + C)^2) 的一条直线,其纵截距为dp[i] - f[i]^2。显然我们想让这个值最小,我们使用类似线性规划的思路,知道当这个值最小的时候决策点应该在一个下凸壳上,就可以用单调队列维护这个凸包。
对于决策点j和k,规定k > j,如果k比j的决策更优,在这个题里面就是
dp[k] + (f[i] - f[k] - C)^2 < dp[j] + (f[i] - f[j] - C)^2
移项,我们会得到
dp[k] - dp[j] + (f[k] - C)^2 - (f[j] - C)^2 - 2f[i](f[k] - f[j]) > 0
即
\frac{dp[k] - dp[j] + (f[k] - C)^2 - (f[j] - C)^2}{2(f[k] - f[j])} > f[i]
这就是最终我们来判断单调队列中哪些点需要弹出的依据,其式子也形似斜率(实际上就是前面我们求出来的直线的斜率)。我们总是保持单调队列队首的决策点对于当前最优,而插入是把不如当前点优的点删去。因为f[i]是单调递增的,单调队列的维护也有了依据。
每一次转移用单调队列队首的点去转移即可。
// Code by KSkun, 2018/3
#include <cstdio>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}
inline int readint() {
register int res = 0, neg = 1;
char c = fgc();
while(c < '0' || c > '9') {
if(c == '-') neg = -1;
c = fgc();
}
while(c >= '0' && c <= '9') {
res = res * 10 + c - '0';
c = fgc();
}
return res * neg;
}
const int MAXN = 50005;
int n, c, l, r, q[MAXN];
LL f[MAXN], dp[MAXN];
inline double sqr(LL x) {
return x * x;
}
inline double slope(int x, int y) {
return (dp[y] - dp[x] + sqr(f[y] + c) - sqr(f[x] + c)) / (2.0 * (f[y] - f[x]));
}
int main() {
n = readint();
c = readint() + 1;
for(int i = 1; i <= n; i++) {
f[i] = f[i - 1] + readint();
}
for(int i = 1; i <= n; i++) {
f[i] += i;
}
l = 1;
r = 0;
q[++r] = 0;
for(int i = 1; i <= n; i++) {
while(l < r && slope(q[l], q[l + 1]) <= f[i]) l++;
dp[i] = dp[q[l]] + sqr(f[i] - f[q[l]] - c);
while(l < r && slope(q[r], i) < slope(q[r - 1], q[r])) r--;
q[++r] = i;
}
printf("%lld", dp[n]);
return 0;
}
Copyright © 2017-2022 KSkun's Blog.
Authored by KSkun and his friends.
本博客内所有原创内容采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。引用内容如果侵权,请在此留言。
All original content in this blog is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
If any reference content infringes your rights, please contact us.