BZOJ 1010 - 斜率优化

本文详细解析了一道经典的DP题目,通过引入斜率优化的方法,有效地解决了原问题中因枚举导致的时间复杂度过高的问题。文章给出了完整的实现代码,并通过理论推导验证了决策单调性的正确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

比较裸的DP+斜率优化啦…… 让窝又想到了BZOJ上A的第一道有意义的题1597…… 作为第27个A的题也让我颇有感触……
设前i个玩具放置到j个盒子里所需的最小费用为f[i][j]。由于连续的玩具必须放到一个容器里,所以我们有:

f[i][j]=f[k][j1]+cost[k+1][i]
其中k为我们枚举的上一个容器的末玩具编号。然后cost[k+1][i]用前缀和搞搞也可以O(1)弄出来:cost[i+1][j]=(ji+sum[j]sum[i]L1)2。但枚举k肯定会TLE… (诶下面这都是些什么…
(Transformers Lost Energy)
(Teenagers Lack Entertainment)
(Teenagers Lost Eggs)
()
所以我们需要优化。
首先把第二维去掉:完全不需要。然后参阅了黄学长博客,对斜率优化有了更深的理解。
观察cost的表达式,我们令d[i]=sum[i]+it=L+1(少写几个字…),则有
f[k]+(d[j]d[k]t)2<f[p]+(d[j]d[p]t)2

第一步,证明决策单调性。什么意思呢?我们设f[i]f[k]转移比从f[p]转移来要优,且p<k,那么对于f[i]之后的任意状态f[t],都有从f[k]转移比从f[p]转移来要优。证明并不难,只要设d[t]=d[i]+s然后搞搞就行了。
第二步,解出斜率方程。
f[k]+cost[k+1][j]<f[p]+cost[p+1][j]
则有
f[k]+(d[j]d[k]t)2<f[p]+(d[j]d[p]t)2
由于我们已经获得了单调递增的函数d,化简移项一番(把d[i]移到不等式的一边),得到:
(f[k]+(d[k]+t)2)(f[p]+(d[p]+t)2)2×(d[k]d[p])<=d[i]
有了上述这些性质,我们用单调队列维护一个下凸包即可。具体来说,每次取出i作为当前决策点,然后考虑当前队列。如果(Q[head],Q[head+1])的斜率大于d[i],那么将Q[head]出队,直到(Q[head],Q[head+1])的斜率小于d[i];然后取出Q[head],显然它就是当前决策点。然后再将i元素加入队列。这就要求(Q[tail],i)的斜率大于(Q[tail1],Q[tail])(单调队列的性质)。状态值f在维护队列的过程中顺便维护即可。
一开始没有用long long,然后后来打补丁的时候又各种地方忘改成long long……
// BZOJ 1010

#include <cstdio>
#include <cstring>
using namespace std;

 const int N=50000+5;

 #define LL long long
 #define read(x) scanf("%d", &x)
 #define rep(i,a,b) for (int i=a; i<=b; i++)

 int n, c, L, Q[N];
 LL f[N], d[N], t;

 LL sqr(LL x) { return x*x; }

 double calc(int p, int k) {
    return (double)(f[k]+sqr(d[k]+t)-(f[p]+sqr(d[p]+t)))/(double)(2.0*(d[k]-d[p]));
 }

int main()
{
    read(n); read(L);
    d[0]=0;
    rep(i,1,n) read(c), d[i]=d[i-1]+c;
    rep(i,1,n) d[i]+=i;
    int head=1, tail=1;
    Q[1]=0; f[0]=0;
    t=L+1;
    rep(i,1,n) {
        while (head<tail && calc(Q[head], Q[head+1])<=d[i]) head++;
        int x=Q[head];
        f[i]=f[x]+sqr(d[i]-d[x]-t);
        while (head<tail && calc(Q[tail-1], Q[tail])>calc(Q[tail], i)) tail--; 
        Q[++tail]=i;
    }
    printf("%lld\n", f[n]);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值