P3195

[HNOI2008]玩具装箱

设 $pre(i)=\sum_{i=1}^{i}c_i$。

则秒出状态转移方程:

$$f(i)=\min_{j<i}{f(j)+(i-j-1-L+pre(i)-pre(j))^2}$$

然后写上就能拿到 20pts 的好成绩。

我们考虑把这个东西的 $i$ 与 $j$ 分离出来。

其实不化简也可以,但我们还是化简一下。

设 $x(i)=pre(i)+i$,$L’=L+1$ 然后原转移方程变成了:

$$f(i)=\min_{j<i}{f(j)+(x(i)-x(j)-L’)^2}$$

把只和 $i$ 有关的移项到左边,其余的都放在右边:

$$f(i)-(x(i)-L’)^2=\min_{j<i}{f(j)+x(j)^2+2x(j)(L’-x(i))}$$

我们把这个东西想象成一个 $b=y-kx$,让它变成一个固定 $k$,且使 $b$ 尽量最优的一个东西,于是只和 $i$ 有关的东西揉在一起变成 $b(i)$,只和 $j$ 有关的东西变成 $y(j)$,和 $i$ 与 $j$ 都有关的东西拆成 $-k(i)x(j)$ 的形式,所以 $k(i)=-2(L’-x(i))$,然后 $x(j)$ 就是 $x(j)$。

于是就变成了 $b(i)=y(j)-k(i)x(j)$,我们要找到一个点 $(x(p),y(p))$ 使得斜率为 $k(i)$ 的直线经过这个点时截距尽量小。

我们这个题性质比较好,所以考虑直接用单调队列来维护。

我们考虑维护一个下凸壳,这个斜线从下往上移动第一个碰到的点就是最优决策点。

显然满足前面一截线段的斜率小于等于 $k(i)$,后面一截大于 $k(i)$。

而最好的一点就是这个 $k(i)$ 是递增的,就是说我们的这个决策可以用单点队列维护。

最优决策点之前的点显然可以全部弹出。

最后把 $(x(i),y(i))$ 这个点加进去,显然把最后一截斜率大于新加该点斜率的点从队尾弹出(维护下凸壳和最优性)。

然后我们就 $O(n)$ 解决了。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll __int128
using namespace std;

const ll N=5e4;

ll n,L;
ll c[N+5],pre[N+5],f[N+5],q[N+5];
ll x[N+5],y[N+5],k[N+5];

inline double Calc_Slope(ll a,ll b) {
return (double)(y[b]-y[a])/(x[b]-x[a]);
}

inline ll read() {
ll ret=0,f=1;char ch=getchar();
while(ch<48||ch>57) {if(ch==45) f=-f;ch=getchar();}
while(ch>=48&&ch<=57) {ret=(ret<<3)+(ret<<1)+ch-48;ch=getchar();}
return ret*f;
}

inline void write(ll x) {
static char buf[22];static ll len=-1;
if(x>=0) {do{buf[++len]=x%10+48;x/=10;}while(x);}
else {putchar(45);do{buf[++len]=-(x%10)+48;x/=10;}while(x);}
while(len>=0) putchar(buf[len--]);
}

int main() {

n=read();L=read();L++;

for(ll i=1;i<=n;i++) {
c[i]=read();pre[i]=pre[i-1]+c[i];
x[i]=pre[i]+i;k[i]=-2*(L-x[i]);
}

ll h=0,t=0;
for(ll i=1;i<=n;i++) {
while(h+1<=t&&Calc_Slope(q[h],q[h+1])<=k[i]) h++;
f[i]=f[q[h]]+(x[i]-x[q[h]]-L)*(x[i]-x[q[h]]-L);
y[i]=f[i]+x[i]*x[i];
while(h+1<=t&&Calc_Slope(q[t],i)<=Calc_Slope(q[t-1],q[t])) t--;
q[++t]=i;
}

write(f[n]);

return 0;
}