斜率优化真他妈好玩
原题: https://www.lydsy.com/JudgeOnline/problem.php?id=1010
题意:
1…N的N件玩具长度为Ci。分成若干段。每段的长度将为
x
=
j
−
i
+
∑
k
=
i
j
C
k
x=j-i+\sum_{k=i}^jCk
x=j−i+∑k=ijCk
长度为x,其制作费用为(X-L)^2.其中L是一个常量。求费用的最小值。
解析
递推式: d p [ i ] = d p [ j ] + ( i − j − 1 + s u m i − s u m j − L ) 2 dp[i]=dp[j]+(i-j-1+sum_i-sum_j-L)^2 dp[i]=dp[j]+(i−j−1+sumi−sumj−L)2
对于两点 j , k ( j < k ) j,k(j<k) j,k(j<k),使k的值更优得: d p [ j ] + ( i − j − 1 + s u m i − s u m j − L ) 2 > d p [ k ] + ( i − k − 1 + s u m i − s u m k − L ) 2 dp[j]+(i-j-1+sum_i-sum_j-L)^2>dp[k]+(i-k-1+sum_i-sum_k-L)^2 dp[j]+(i−j−1+sumi−sumj−L)2>dp[k]+(i−k−1+sumi−sumk−L)2 d p [ j ] + ( j + s u m j ) 2 − 2 ( i + s u m i − L − 1 ) ( j + s u m j ) > d p [ k ] + ( k + s u m k ) 2 − 2 ( i + s u m i − L − 1 ) ( k + s u m k ) dp[j]+(j+sum_j)^2-2(i+sum_i-L-1)(j+sum_j)>dp[k]+(k+sum_k)^2-2(i+sum_i-L-1)(k+sum_k) dp[j]+(j+sumj)2−2(i+sumi−L−1)(j+sumj)>dp[k]+(k+sumk)2−2(i+sumi−L−1)(k+sumk) d p [ j ] − d p [ k ] + ( j + s u m j ) 2 − ( k + s u m k ) 2 > 2 ( i + s u m i − L − 1 ) ( j + s u m j − ( k + s u m k ) ) dp[j]-dp[k]+(j+sum_j)^2-(k+sum_k)^2>2(i+sum_i-L-1)(j+sum_j-(k+sum_k)) dp[j]−dp[k]+(j+sumj)2−(k+sumk)2>2(i+sumi−L−1)(j+sumj−(k+sumk)) d p [ j ] − d p [ k ] + ( j + s u m j ) 2 − ( k + s u m k ) 2 j + s u m j − ( k + s u m k ) < 2 ( i + s u m i − L − 1 ) \dfrac{dp[j]-dp[k]+(j+sum_j)^2-(k+sum_k)^2}{j+sum_j-(k+sum_k)}<2(i+sum_i-L-1) j+sumj−(k+sumk)dp[j]−dp[k]+(j+sumj)2−(k+sumk)2<2(i+sumi−L−1)
验证正确性并得出单调性:
首先因为是小于号,所以队列中值为大于 2 ( i + s u m i − L − 1 ) 2(i+sum_i-L-1) 2(i+sumi−L−1)的数。
而右侧值随着i变大而变大,在变大后,可能队首的值不符合要求要弹出(小于 2 ( i + s u m i − L − 1 ) 2(i+sum_i-L-1) 2(i+sumi−L−1))
结论:
化解过程正确,单调性为:增;队列中的值大于 2 ( i + s u m i − L − 1 ) 2(i+sum_i-L-1) 2(i+sumi−L−1)。
细节:
由于第二个点也可以从0转移,所以需要压入0这个状态。
代码:
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int maxn=50009;
LL n,L;
LL a[maxn];
LL dp[maxn];
LL sum[maxn];
int Q[maxn],l=1,r=1;
double check(int j,int k){
return double(dp[j]-dp[k]+(j+sum[j])*(j+sum[j])-(k+sum[k])*(k+sum[k]))/(double)((j+sum[j])-(k+sum[k]));
}
int main(){
scanf("%lld%lld",&n,&L);
for(int i=1;i<=n;i++){
scanf("%d",a+i);
sum[i]=sum[i-1]+a[i];
}
dp[0]=0;
Q[1]=0;
for(int i=1;i<=n;i++){
while(r>l&&check(Q[l],Q[l+1])<2*(i-L+sum[i]-1))l++;
int j=Q[l];
dp[i]=dp[j]+(i-j-1+sum[i]-sum[j]-L)*(i-j-1+sum[i]-sum[j]-L);
//printf("%d -> %d dp[%d]=%lld\n",j,i,i,dp[i]);
while(r>l&&check(Q[r-1],Q[r])>check(Q[r],i))r--;
Q[++r]=i;
}
printf("%lld\n",dp[n]);
}