题解 | 信息学奥赛一本通 - 玩具装箱

E-玩具装箱_Part5.6 动态规划-斜率优化动态规划

https://ac.nowcoder.com/acm/contest/976/E

斜率优化DP

前置姿势

1. 单调队列,DP,以及单调队列优化DP

单调队列用来维护一个滑动区间的最大值,因为最多滑动不超过2n次,复杂度O(n).

P1886 滑动窗口是单调队列的模板题。

在线性DP很常用

P3957 跳房子 就是一道经典的单调队列优化动归...

(当时在考场上遇到这道题毫无头绪,现在却变成了简单题....想起来感慨很深啊...

为到第的花费,依次遍历

因为每个点是由一个区间转移过来的,而这个区间随着i的右移也右移,所以满足单调队列优化的条件,优化后可达到

P3572 [POI2014]PTA-Little Bird 与上道题基本一样...

2.一次函数

初中基本知识不谈

正文

这里有一个经典的问题

有一个长度为n的正整数序列,要求划分成若干段,每一段的代价为这段的和的平方+m,求划分序列的最小代价

考虑m=0,显然划分越多次越好,直接划成n段

但是有了m,划分多一段,代价就会多m,划分成n段是不划算的

先考虑的DP做法

为原序列前缀和, 为划分前k个数最小代价,显然有

对于每一个,枚举断点即可,时间复杂度

斜率优化要对上面这个式子进行优化

首先复读一遍式子

用完全平方公式展开

发现怎么取值,都一定不变

移项,得

(取一个k,使得最小)

将上面的式子转为一次函数

那么即为一条过,斜率为的直线,在y轴上的截距,因为都是常量,所以当最小,即最小

那么对于每个点i,前面就有个断点,我们要在这个点钟找到一个点,使得截距最小(也就是最小)

再公式化地表述 就是有一条过,斜率为的直线,截距为,找一个点使得截距最小

如果有三个点表示横坐标,且在直线的上方,那么B肯定不是最优决策

红色线为最优决策,蓝色线为选B时的决策

发现选B永远不如选A或C好(截距较大、注意截距可以为负数)

那么所有的有可能对答案有贡献的点,会形成一个下凸壳

(下凸壳就是对于任意三点(按横坐标排序),不在的连线上方)

类比于单调队列,我们每次加入一个可能可以转移的状态,就可以删掉一些无用的状态

我们发现,我们每次的,即横坐标,是单调上升的(因为是正整数序列的前缀和),所以我们加入的决策点一个比一个偏右

这就类似于单调队列,下面模拟一下删点的过程

这是我们原来的图,红色为新加的点

连线,发现我们倒数第二个点不满足下凸壳,删去

继续连线

发现下一个点也不满足下凸壳,删去

最后连一次线,发现满足

最后的形状

另外,我们可以证明,若第个点满足下凸壳,则这些点全部满足下凸壳。

我们发现这个过程和单调队列几乎完 全 一 致,类似于单调队列,这个过程也可以用deque来维护,每个点至多进队一次,出队一次,这样扫过去的复杂度是

以上就是加点的过程,接下来我们来看如何找到最小截距

我们发现,在这道题中我们加入的点的横坐标和直线的斜率都是递增的

再重新看一下我们的任务吧...

有一条过,斜率为的直线,截距为,找一个点使得截距最小

接下来我们要找一个点使得截距最小

我们的方法是把队首弹出直至队首两个点的斜率大于等于,此时队首就是最优决策点

按上面的步骤做即可

其实这个找最优决策点的过程,类似于线性规划

玩具装箱

回到本题,先计算出前缀和

那么有

枚举断点,得到一个n方的状态转移方程,时间复杂度不正确

考虑斜率优化,令

得到式子

展开,移项

,发现是一个可以斜率优化的式子,线性规划即可

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<string>
#include<cstdio>
#include<vector>
#include<queue>
#include<cmath>
#include<queue>
#include<map>
#include<set>

using namespace std;

typedef double db;
typedef long long ll;

inline int read() {
    int res=0,fh=1;
    char ch=getchar();
    while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
    if(ch=='-')fh=-1,ch=getchar();
    while(ch>='0'&&ch<='9')res=res*10+ch-'0',ch=getchar();
    return fh*res;
}

const int maxn=100010;
int n,l;
db sum[maxn],f[maxn];
int hd,tl,q[maxn];
inline db a(int i){return sum[i]+i;}
inline db b(int i){return a(i)+l+1;}
inline db X(int i){return b(i);}
inline db Y(int i){return f[i]+b(i)*b(i);}
inline db slope(int i,int j){
    return (Y(i)-Y(j))/(X(i)-X(j));
}

int main()
{
    n=read(),l=read();
    for(int i=1;i<=n;i++){
        scanf("%lf",&sum[i]);
        sum[i]+=sum[i-1];
    }
    hd=tl=1;
    for(int i=1;i<=n;i++){
        while(hd<tl&&slope(q[hd],q[hd+1])<2*a(i)) hd++;
        f[i]=f[q[hd]]+(a(i)-b(q[hd]))*(a(i)-b(q[hd]));
        while(hd<tl&&slope(i,q[tl-1])<slope(q[tl-1],q[tl])) --tl;
        q[++tl]=i;
    }
    printf("%.0lf\n",f[n]);
    return 0;
}
全部评论

相关推荐

勤奋努力的椰子这就开摆:美团骑手在美团工作没毛病
投递美团等公司10个岗位
点赞 评论 收藏
分享
喜欢走神的孤勇者练习时长两年半:池是池,发是发,我曾池,我现黑
点赞 评论 收藏
分享
1 收藏 评论
分享
牛客网
牛客企业服务