[BZOJ]1010
题意:
P教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京。他使用自己的压缩器进行压
缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中。P教授有编号为
1...N
的
N
件玩具,第i件玩具经过
压缩后变成一维长度为
器中有多个玩具,那么两件玩具之间要加入一个单位长度的填充物,形式地说如果将第
i
件玩具到第
个容器中,那么容器的长度将为
x=j−i+Sigma(Ck),i<=K<=j
制作容器的费用与容器的长度有关,根据教授研究,
如果容器长度为x,其制作费用为
(X−L)2
.其中
L
是一个常量。P教授不关心容器的数目,他可以制作出任意长度的容
器,甚至超过
题解:
n2
是直接Dp就可以。但是复杂度过不去呢。。
此题优化及其玄学。
首先我们写出来
n2
的方程感受一下。。
大概就是这么个东西。什么也看不出来对吧/(ㄒoㄒ)/我们来简化一下这个式子。
设 C=L+1,g[i]=sum[i]+i ,那么上式又等于
变成了这样的一个式子之后,之后考虑优化(摔)
在斜率优化中有这样一类问题:如果递推式可以变成形如
且 t[i] 和 k[i] 是单调的!就可以用单调队列维护一个凸壳来取答案。
而我们简化后的式子又等于下面这个东西(即展开后)
看起来很鬼畜对吧。观察一下这个式子可以发现完全满足上面我们的条件。
那么我们推一个斜率公式。首先假设 i 的前面有两个点叫
我们解这个式子最后可以得到
转化成了一个斜率的形式!可他有什么用处呢?这个式子代表着对于 j<k,Slope(j,k)>g[i] 说明对于 i 来说
我们可以把每一个决策以一个
这样的形式构建在坐标系中(意会,意会…),大概长这个样子。
注意这些都是已经得到答案的点,我们现在要选一个点转移到我们现在要求的 dp[i] ,有一个结论是可能是决策点的点只能在下凸壳上也就是
大概这样子,为什么呢?
随意考虑一个不在下凸壳上的点,他后面的点有两种情况,一种是纵坐标比它大,一种是纵坐标比它小,我们分别举个例子。比如左边第二个点,它后面有纵坐标比它小的在凸壳上的点,它与这个点连线的斜率显然是小于0的,而 g[i] 却是绝对大于0的,所以这个凸壳上的点一定更优。
再看右边第二个点,这个利用反证法证明这个点不会是最优的。假设这个点是最优的,那么它与后面纵坐标比它大的凸壳上的点斜率就大于 g[i] ,那么它前面纵坐标比他小的凸壳上的点与它的连线斜率一定也大于 g[i] (结合图去看),也就是说这个纵坐标比他小的凸壳上的点比它更优,与假设矛盾,故不成立。
这样经证明,得出我们的答案一定在这个下凸壳上。
可是这有什么用呢?用处很大。先假设我们已经得到了一些下凸壳上的点(具体怎么得到等会再说),要求 dp[i] 的答案,就要去这些下凸壳上的点找一个最优的拿出来,怎么拿?这么拿。设 i 为第一个点。
1. 看
2. 是的话 i 就是最优的那个点。因为如果再去比较
3. 不是的话那么好,这个 i 以后也不会成为答案了,可以直接从下凸壳里删除掉。因为
这是一个取答案的过程。每个决策在被凸壳删除后再也不会入下凸壳,所以复杂度为
O(n)
。在这个过程中我们得到了
dp[i]
的值,我们要把它放到凸壳里。
下面介绍如何造一个凸壳!下面写出步骤。
1. 如果凸壳是空的放进去= =
2. 设最后一个点为
t
,现在要加进去的是
3. 知道满足条件把
i
<script type="math/tex" id="MathJax-Element-465">i</script>加进去这样出来的一定是凸壳。
这样算法就很明了了,用一个队列维护一个凸壳,从凸壳中取答案即可。
(上面一大堆乱七八糟的证明其实就是这一句话)
代码很短主要是斜率dp的思想需要了解一下。
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
typedef long long LL;
using namespace std;
const int Maxn = 50010;
int q[Maxn],n,l,c;
LL a[Maxn],dp[Maxn],g[Maxn];
double Slope(int k,int j){//斜率公式
return ( (dp[j] + g[j]*g[j] + 2 * c * g[j]) - (dp[k] + g[k] * g[k] + 2 * c * g[k] ) ) / (2.0* (g[j] - g[k]) );
}
int main(){
scanf("%d%d",&n,&l);c = l + 1;
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
g[i] = g[i-1] + a[i];
}
for(LL i=1;i<=n;i++)g[i] += i;
int h = 1,t = 0;q[++t] = 0;
for(int i=1;i<=n;i++){//维护单调队列
while(h < t && Slope(q[h],q[h+1]) <= g[i])h++;
int w = q[h];//从队首取出答案
dp[i] = dp[w] + (g[i] - g[w] - (LL)c) * (g[i] - g[w] - (LL)c);
while(h < t && Slope(q[t],i) < Slope(q[t-1],q[t]))t--;
q[++t] = i;//维护下凸性并加入i点
}printf("%lld\n",dp[n]);
// while(1);
return 0;
}