洛谷P3195 [HNOI2008]玩具装箱TOY
13 April 2019 |
Duanyll | Tags:
OI
题解
DP
经典的一道斜率优化DP,很久以前写的,现在再拿出来复习一下
简单读题可以得出本题的DP方程是
\[dp[i] = min(dp[j]+(sum[i]-sum[j] + i - j - 1 - L)^2)\]但是这样转移的复杂度高达$O(n^2)$,5e4的数据不能接受,需要优化.
为了简便计算,令$s[i]=sum[i]+i,C=L+1$.
\[dp[i] = dp[j] + (s[i]-s[j]-C)^2\]假设存在决策$j$和$k$($1\leq j < k < i$),使得$k$比$j$更优,则有
\[dp[j] + (s[i]-s[j]-C)^2 \geq dp[k] + (s[i]-s[k]-C)^2\]展开式子得
\[dp[j] + s[i]^2 - 2*s[i]*(s[j] + C) + (s[j] + C)^2 \geq dp[k] + s[i]^2 - 2*s[i]*(s[k] + C) + (s[k] + C)^2\]移项
\[2*s[i]*(s[k] - s[j]) \geq dp[k]-dp[j] + (s[k]+C)^2-(s[j]+C)^2\]把变量$s[k],s[j]$除过去
\[2*s[i] \geq \frac{dp[k]-dp[j] + (s[k]+C)^2-(s[j]+C)^2}{s[k] - s[j]}\]为了看得更清楚,再令$h[i]=(s[i]+C)^2$
\[2*s[i] \geq \frac{dp[k]+h[k] - dp[j]-h[j]}{s[k] - s[j]}\]经过这样的变换,这个DP方程就有了个斜率样子了:如果把$dp[j]+h[j]$看做纵坐标,把$s[j]$看做横坐标,那么$2*s[i]$就是过这两点的直线的斜率.
再重申一遍,只要满足上面这个式子(考虑每一个i时,左侧是一个常量!),决策k就比决策j更优.
由于$2*s[i]$是单调递增的,所以如果我们把$(s[j],dp[j]+h[j])$对应的点依次绘制在坐标系上,就会构成一个函数(随便想一个函数的图像).每当我们将一个点从右侧连接到这个图像上是,就可以知道它与上一个点之间的斜率,再比较上上个点与上一个点的斜率,如果这两个斜率是递减的,说明当前决策一定更有可能比上一个决策最优!
这像不像一个单调队列?我们已经有了进队的条件了,可以维护一个优秀程度递增(在j递增的基础上)的单调队列了.
那么出队呢?很好的是,$2s[i]$也是单调递增的.所以我们只要把当前看起来不会更优的点(队首斜率大于$2s[i]$时)出队,让当前可以更优的点成为队首.
所以像这样能用单调队列解决的关键点:
- 能把两个决策点优劣的关系式化成斜率式
- 式子左边对i具有单调性
- 式子右边视作x坐标的项对j具有单调性
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
typedef long long int64;
const int MAXN = 50050;
int64 a[MAXN];
int64 n,L;
int64 sum[MAXN],s[MAXN],h[MAXN];
int64 que[MAXN];
int64 dp[MAXN];
double cal(int j,int k){
return (double)(dp[k]-dp[j] + h[k]-h[j])/(s[k] - s[j]);
}
int main(){
cin >> n >> L;
for(int i = 1;i<=n;i++){
cin >> a[i];
sum[i] = sum[i-1] + a[i];
s[i] = sum[i] + i;
h[i] = (s[i]+L+1) * (s[i]+L+1);
}
h[0] = (L+1) * (L+1);
int head = 0,tail = 0;
for(int i = 1;i<=n;i++){
while(head < tail && cal(que[head],que[head+1]) <= 2*s[i]){
head++;
}
dp[i] = dp[que[head]] + (s[i]-s[que[head]]-L-1)*(s[i]-s[que[head]]-L-1);
while(head < tail && cal(que[tail],i) < cal(que[tail-1],que[tail])){
tail--;
}
que[++tail] = i;
}
cout << dp[n] << endl;
}