P3195 [HNOI2008]玩具装箱TOY(斜率优化DP)

题目链接:https://www.luogu.org/problem/P3195

 

题目大意:有n个物品,每个物品都有一个权值,将物品分为若干堆,定义x=j-i+\sum_{k=i}^{j}C_{k},则每一堆的价值为(x-L)^{2},求总价值的最小值是多少。

 

题目思路:易得dp方程dp[i]=min(dp[j]+(sum[i]-sum[j]+i-j-1-L)^{2})(j<i)可以发现这是一个n^2的式子,直接dp就超时了,所以就需要用到斜率优化。首先简化该式,设f[i]=sum[i]+i,上式转换为dp[i]=min(dp[j]+(f[i]+f[j]-1-L)^{2})(j<i)

设j1<j2,那么当dp[j_{1}]+(f[i]-f[j_{1}]-1-L)^{2}\geq dp[j_{2}]+(f[i]-f[j_{2}]-1-L)^{2}

拆解该不等式得到:

dp[j_{1}]-2f[i]f[j_{1}]+(f[j_{1}]+1+L)^{2}\geq dp[j_{2}]-2f[i]f[j_{2}]+(f[j_{2}]+1+L)^{2}

2f[i](f[j_{2}]-f[j_{1}])\geq dp[j_{2}]+(f[j_{2}]+1+L)^{2}-(dp[j_{1}]+(f[j_{1}]+1+L)^{2})

2f[i]\geq \frac{dp[j_{2}]+(f[j_{2}]+1+L)^{2}-(dp[j_{1}]+(f[j_{1}]+1+L)^{2})}{f[j_{2}]-f[j_{1}]}

所以就可以发现,当前一个点和后一个点的斜率小于2*f[i]时,后面一个决策点要好于上一个,同时由该式可以得到一个斜率式,以f[i]为x,{dp[i]+(f[i]+1+L)^{2}为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;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值