BZOJ1010: [HNOI2008]玩具装箱toy(斜率优化以及斜率优化套路)

11 篇文章 0 订阅
2 篇文章 0 订阅

题目

将n个元素进行分组,每个元素都有一个权值w[i],每组的元素必须是一段连续的元素,将i~j 这一组组带来的费用是 (jk=iw[k]L)2 ( ∑ k = i j w [ k ] − L ) 2 ,求最小费用

分析

老子终于写了一道斜率优化啦!!!!

先说一下斜率优化的一般步骤:
1. 推出DP方程,大概是f[i=min{f[j]+g(i,j)}的形式,max也是可以的
2. 令 k<j<i k < j < i ,看j在什么时候比K更优,使劲化简,用各种方法替代啊什么的,最终弄成一个 y(j)y(k)x(j)x(k)>=s(i) y ( j ) − y ( k ) x ( j ) − x ( k ) >= s ( i ) 的形式,注意x(),y(),是两个函数,s也是函数。
3. 看看x()有没有单调性,确定用平衡树维护还是用单调队列,然后画几个例子看一下是维护上凸包还是下凸包,列出一个凸包,如果答案在凸包上而不是在图标两端一般就说明是对的。
4. 用一个队列维护凸包,一般来说对于有些单调性的情况,一个点一旦失效就没有用了,所以实际上像是个单调队列;如果x(),y()都没有单调性,考虑有set维护凸包;如果s()也没有单调性,那么就用三分/set进行查找。

注意:
1. 公式必须认认真真推导,因为你会懒得去手算样例。
2. 对拍很重要,小数据经常随便乱维护都是对的。
3. 维护凸包的时候一般要保证至少还有两个元素。
4. 注意维护凸包用叉积还是斜率,可以用斜率就用斜率无妨,但是存在没有斜率或者太大的时候就要小心了。
5. 我觉得x或者y应该是可以换的,只需要有一个有单调性就可以了。

说罢,小生决定去找一个动态维护凸包的情况,然后发现了一点新天地(滑稽)。
然后发现有一种神奇的方式,CDQ分治!!!
就是说递归解决左边的,你就可以肆无忌惮地把左边排序然后构造一个凸包出来啦!就可以去更新一波右边的答案。
这样的CDQ分治会使得一般有单调性的从O( n n )变成O(nlogn)
但是,对于没有单调性的,如果你每次重新排序x,那么的确变成了O( nlog2n n l o g 2 n ),不过既然在处理的时候按照x拍好序了,那么归并即可,还是O( nlogn n l o g n )
最后,如果s不单调,可以 选择把s排序或者使用三分、set在凸包上面找了,但是这样无论如何时间复杂度都变成了O( nlog2n n l o g 2 n ),就要比直接DPO(nlogn)慢一点啦。

回到这道题,出于懒惰不想打公式,去看其他人的题解吧(手动滑稽)
哈哈哈哈,放风筝的感觉哎QAQ

代码

#include<cmath>
#include<queue>
#include<cctype>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=4e5+105;
int n,L;
LL sum[maxn],s[maxn],x[maxn],y[maxn],f[maxn];
int q[maxn],front,rear;
void Init()
{
    scanf("%d%d",&n,&L);
    int c;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&c);
        sum[i]=sum[i-1]+c;
        s[i]=s[i-1]+c+1;
    }
}
#define calc(a,b) (double)(y[a]-y[b])/(x[a]-x[b])
void dp()
{
    int j;
    q[rear++]=0;
    for(int i=1;i<=n;i++)
    {
        while(rear-front>1 && 2*(s[i]-(L+1))>=calc(q[front],q[front+1]) )front++;
        j=q[front];
        f[i]=f[j]+(i-j-1+sum[i]-sum[j]-L)*(i-j-1+sum[i]-sum[j]-L);
        y[i]=s[i]*s[i]+f[i];x[i]=s[i];
        while(rear-front>1 && calc(q[rear-1],i) <= calc(q[rear-1],q[rear-2]) )rear--;
        q[rear++]=i;
    }
    printf("%lld\n",f[n]);
}
int main()
{
    //freopen("in.txt","r",stdin);
    Init();
    dp();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值