[HNOI2008]玩具装箱TOY
problem
Solution
20’DP:
d p [ i ] = m i n ( d p [ j ] + ( s u m [ i ] − s u m [ j − 1 ] + i − j − L ) 2 ) dp[i] = min(dp[j] + (sum[i] - sum[j - 1] + i - j - L)^2) dp[i]=min(dp[j]+(sum[i]−sum[j−1]+i−j−L)2)
code:
#include<bits/stdc++.h>
#define N 500005
#define int long long
using namespace std;
int sqr(int x){
return x * x;
}
int n, L, a[N], sum[N], f[N];
signed main(){
scanf("%lld%lld", &n, &L);
for(int i = 1; i <= n; i ++) scanf("%lld", &a[i]), sum[i] = a[i] + sum[i - 1];
memset(f, 0x3f, sizeof f);
f[0] = 0;
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= i; j ++){
f[i] = min(f[i], f[j - 1] + sqr(sum[i] - sum[j - 1] + i - j - L));
}
}
printf("%lld", f[n]);
return 0;
}
斜率优化
可以变成
d
p
[
i
]
=
m
i
n
(
d
p
[
j
]
+
(
s
u
m
[
i
]
−
s
u
m
[
j
]
+
i
−
j
−
L
−
1
)
2
)
dp[i] = min(dp[j] + (sum[i] - sum[j] + i - j - L - 1)^2)
dp[i]=min(dp[j]+(sum[i]−sum[j]+i−j−L−1)2)
令 a [ i ] = s u m [ i ] + i , b [ i ] = s u m [ i ] + i + L + 1 a[i] = sum[i] + i, b[i]=sum[i]+i+L+1 a[i]=sum[i]+i,b[i]=sum[i]+i+L+1
d p [ i ] = m i n ( d p [ j ] + ( a [ i ] − b [ j ] ) 2 ) dp[i] = min(dp[j] + (a[i]-b[j])^2) dp[i]=min(dp[j]+(a[i]−b[j])2)
d p [ i ] = d p [ j ] + a [ i ] 2 + b [ j ] 2 − 2 ⋅ a [ i ] ⋅ b [ j ] dp[i] = dp[j] + a[i]^2+b[j]^2-2·a[i]·b[j] dp[i]=dp[j]+a[i]2+b[j]2−2⋅a[i]⋅b[j]
2 ⋅ a [ i ] ⋅ b [ j ] + d p [ i ] − a [ i ] 2 = d p [ j ] + b [ j ] 2 2·a[i]·b[j] + dp[i] -a[i]^2= dp[j]+b[j]^2 2⋅a[i]⋅b[j]+dp[i]−a[i]2=dp[j]+b[j]2
把 b [ j ] b[j] b[j]看做 x x x, d p [ j ] + b [ j ] 2 dp[j]+b[j]^2 dp[j]+b[j]2看做 y y y
y = 2 ⋅ a [ i ] x + d p [ i ] − a [ i ] 2 y=2·a[i]x+dp[i]-a[i]^2 y=2⋅a[i]x+dp[i]−a[i]2
这就是一条直线的解析式, y = k x + b , k = 2 ⋅ a [ i ] , b = d p [ i ] − a [ i ] 2 y=kx+b, k=2·a[i],b=dp[i]-a[i]^2 y=kx+b,k=2⋅a[i],b=dp[i]−a[i]2
要找一个点 P ( b [ j ] , d p [ j ] + b [ j ] 2 ) P(b[j],dp[j]+b[j]^2) P(b[j],dp[j]+b[j]2)使得上面的斜率为2*a[i]的直线过这个点且与 y y y轴截距 ( b = d p [ i ] − a [ i ] 2 ) (b=dp[i]-a[i]^2) (b=dp[i]−a[i]2)最小
因为斜率 k = 2 ⋅ a [ i ] k=2·a[i] k=2⋅a[i]是固定的,所以要求的就是最小的 b b b,把它加上 a [ i ] 2 a[i]^2 a[i]2就是 d p [ i ] dp[i] dp[i]的值
大概就是这样
就变成了用一条斜率为 k = 2 ⋅ a [ i ] k=2·a[i] k=2⋅a[i]的直线从下到上(或从右到左)扫过去,碰到的第一个点后把直线和 y y y轴的截距求出来加上 a [ i ] a[i] a[i]就是答案
很明显就是维护一个下凸壳(1/4)
然后就单调队列维护嘛。。
设 S l o p e ( i , j ) Slope(i,j) Slope(i,j)为 P i , P j P_i, P_j Pi,Pj 的斜率
如果 S l o p e ( Q h e a d , Q h e a d + 1 ) < = 2 ⋅ a [ i ] Slope(Q_{head}, Q_{head + 1}) <=2·a[i] Slope(Qhead,Qhead+1)<=2⋅a[i]就 h e a d + + head ++ head++
S l o p e ( Q t a i l , Q t a i l − 1 ) > = S l o p e ( Q t a i l − 1 , i ) Slope(Q_{tail}, Q_{tail - 1}) >= Slope(Q_{tail - 1}, i) Slope(Qtail,Qtail−1)>=Slope(Qtail−1,i)就 t a i l − − tail -- tail−−
code:
#include<bits/stdc++.h>
#define N 1000005
#define int long long//hei hei
#define double long double //开long double 防止精度丢失
using namespace std;
int q[N], n, L, sum[N], dp[N];
double a(int i) {return sum[i] + i;}
double b(int i) {return sum[i] + i + L + 1;}
double x(int i) {return b(i);}
double y(int i) {return dp[i] + b(i) * b(i);}
double Slope(int i, int j) {return (y(i) - y(j)) / (x(i) - x(j));}
signed main(){
scanf("%lld%lld", &n, &L);
for(int i = 1; i <= n; i ++) scanf("%lld", &sum[i]), sum[i] += sum[i - 1];
int l = 1, r = 1;
for(int i = 1; i <= n; i ++){
while(l < r && Slope(q[l], q[l + 1]) < 2 * a(i)) l ++;
int j = q[l];
dp[i] = dp[j] + (a(i) - b(j)) * (a(i) - b(j));//截距就是把x=2*b[j]代入斜率为2*a[i]的直线,解除b,加上a[i]就是dp[i],化简完就是这样, 或者直接转移也可以
while(l < r && Slope(q[r - 1], q[r]) > Slope(i, q[r - 1])) r --;
q[++ r] = i;
}
printf("%lld", dp[n]);
return 0;
}