题目链接:https://www.luogu.org/problem/P3195
题目大意:有n个物品,每个物品都有一个权值,将物品分为若干堆,定义,则每一堆的价值为,求总价值的最小值是多少。
题目思路:易得dp方程可以发现这是一个n^2的式子,直接dp就超时了,所以就需要用到斜率优化。首先简化该式,设,上式转换为。
设j1<j2,那么当
拆解该不等式得到:
所以就可以发现,当前一个点和后一个点的斜率小于2*f[i]时,后面一个决策点要好于上一个,同时由该式可以得到一个斜率式,以f[i]为x,为y,所以想要dp[i]足够小,等价于直线的截距要小。由线性规划知识,维护一个下凸壳,由下凸壳的性质,斜率是单调递增的,所以第一个大于等于2*f[i]的点就是最优解。
维护这个需要使用单调队列,因为他具有单调性,后面的都比前面的要好,在更新dp前,先判断首部的斜率,找到第一个斜率大于等于2*f[i]的点,然后用它更新dp,接着在插入i点时,观察单调队列的尾部,如果尾部跟它上一个点构成的直线的斜率大于该点跟i构成的斜率,那么它属于下凸壳内部的点,需要删掉。
以下是代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define rep(i,a,b) for(ll i=a;i<=b;i++)
#define per(i,a,b) for(ll i=a;i>=b;i--)
const ll MAXN = 1e5+5;
ll dp[MAXN],sum[MAXN],f[MAXN],g[MAXN],c[MAXN];
ll n,l,q[MAXN],h,t;
double xie(ll n1,ll n2){
return 1.0*((dp[n2]+(f[n2]+1+l)*(f[n2]+1+l))-(dp[n1]+(f[n1]+1+l)*(f[n1]+1+l)))/(f[n2]-f[n1]);
}
int main()
{
while(~scanf("%lld%lld",&n,&l)){
sum[0]=0;
memset(dp,0,sizeof(dp));
rep(i,1,n){
scanf("%lld",&c[i]);
sum[i]=sum[i-1]+c[i];
f[i]=sum[i]+i;
g[i]=f[i]+1+l;
}
h=t=1;
rep(i,1,n){
while(h<t&&2*f[i]>=xie(q[h],q[h+1]))h++;
dp[i]=dp[q[h]]+(f[i]-f[q[h]]-1-l)*(f[i]-f[q[h]]-1-l);
while(h<t&&xie(q[t-1],q[t])>=xie(q[t],i))t--;
q[++t]=i;
}
printf("%lld\n",dp[n]);
}
return 0;
}