[HNOI]玩具装箱
算是斜率dp的入门题吧,记录一下自己的学习心得。
S[]处理前缀和,可得状态转移方程
F
[
i
]
=
F
[
j
]
+
(
S
[
i
]
−
S
[
j
]
+
i
−
j
−
1
−
L
)
2
,
0
<
=
j
<
i
F[i]=F[j]+(S[i]-S[j]+i-j-1-L)^2,0<=j<i
F[i]=F[j]+(S[i]−S[j]+i−j−1−L)2,0<=j<i
换元简化
a
=
S
[
i
]
+
i
,
b
=
S
[
j
]
+
j
+
L
+
1
a=S[i]+i,b=S[j]+j+L+1
a=S[i]+i,b=S[j]+j+L+1 可得
F
[
i
]
−
a
2
+
2
∗
a
∗
b
=
F
[
j
]
+
b
2
F[i]-a^2+2*a*b=F[j]+b^2
F[i]−a2+2∗a∗b=F[j]+b2
不妨把它看成直线方程
A
x
+
B
=
Y
Ax+B=Y
Ax+B=Y,那么2*a即为该直线斜率。
F
[
i
]
F[i]
F[i]的意义即为斜率为
2
∗
a
2*a
2∗a且过点
(
b
,
F
[
j
]
+
b
2
)
(b,F[j]+b^2)
(b,F[j]+b2)的直线的截距加上
a
[
i
]
2
a[i]^{2}
a[i]2.而如果j1的选择优于j2的选择,用上面的式子做减法可得
F
[
j
1
]
+
b
1
2
−
(
F
[
j
2
]
+
b
2
2
)
b
1
−
b
2
>
2
∗
a
i
\frac{F[j1]+b1^2-(F[j2]+b2^2)}{b1-b2}>2*ai
b1−b2F[j1]+b12−(F[j2]+b22)>2∗ai即连线的斜率要大于直线Y的斜率,且易得
a
a
a是单增的,所以一旦不最优以后也不会最优,再由图像(懒得画)可知要找的是第一个满足
F
[
j
1
]
+
b
1
2
−
(
F
[
j
2
]
+
b
2
2
)
b
1
−
b
2
>
2
∗
a
i
\frac{F[j1]+b1^2-(F[j2]+b2^2)}{b1-b2}>2*ai
b1−b2F[j1]+b12−(F[j2]+b22)>2∗ai的点,并且可以分析出最有可能的点成一个下凸包,于是可以用单调队列优化。
代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e4+5;
int C[maxn],n,L,op[maxn];
ll sum[maxn],F[maxn];
double slope(int x,int y)
{
double bx = sum[x] + x + L + 1;
double by = sum[y] + y + L + 1;
return (F[x]+bx*bx-F[y]-by*by)/(bx-by);
}
ll cost(int x,int y)
{
return (sum[y] - sum[x] + y - x - L - 1)*(sum[y] - sum[x] + y - x - L - 1);
}
int main()
{
//freopen("tte.txt","r",stdin);
scanf("%d %d",&n,&L);
for(int i = 1;i <= n;i++)
{
scanf("%d",&C[i]);
sum[i] = sum[i-1] + C[i];
}
int c = 0,l = 1,r = 1;
op[1] = 0;
for(int i = 1;i <= n;i++)
{
while(r > l&&slope(op[l],op[l+1]) < 2.0 * (sum[i]+i)) l++;
F[i] = F[op[l]] + cost(op[l],i);
//cout<<op[l]<<' '<<F[i]<<endl;
while(r > l&&slope(op[r-1],op[r]) > slope(op[r],i)) r--;
//if(l==r&&slope(0,op[l]) > slope(0,i)) r--;
op[++r] = i;
}
printf("%lld\n",F[n]);
return 0;
}