P3195 [HNOI2008]玩具装箱

#本篇题解是作者学习为学习斜率优化dp而写,其中许多地方参考(或抄)了hhz6830975 的题解,见谅!

题目链接:

P3195 [HNOI2008]玩具装箱

题目大意:

  本题是斜率优化dp经典入门题,适合像我这种小白做.首先我们可以推出转移方程:$$f[i]=min{f[j]+(sum_{i}-sum_{j}+i-j-L-1)^{2}}$$

  由于数据规模十分庞大,接下来我们要讨论的就是如何将这个$O(n^{2})$规模的DP优化成$O(n)$.

solution:

  进入重点,开始起舞吧!首先,我们可以简化该转移方程,由于我们的目标是$O(n)$,所以势必要将$min$函数给去掉.去掉这个$min$,意味着我们需要一步进行决策,所以后面我们需要寻找单调性利用单调队列优化.先进行第一步化简:

$$f_{i}=f_{j}+(sum_{i}-sum_{j}+i-j-L-1)^{2}$$

  为了简化代码,我们令$a_{k}=sum_{k}+k,b_{k}={sum_{k}+k+L+1}$,则原方程可化简为:

$$f_{i}=f_{j}+(a_{i}-b_{j})^{2}$$

$$\therefore 2a_{i}b_{j}+f_{i}-a_{i}^{2}=f_{j}+b_{j}^{2}$$

 

                    inline double a(ll i){return sum[i]+i;}
                    inline double b(ll i){return a(i)+L+1;}

 

 

 

  这里我们可以将$2a_{i}$看作斜率,$b_{j}$看作自变量$x$,$f_{j}+b_{j}^{2}$看作因变量$y$

                    inline double X(ll i){return b(i);}
                    inline double Y(ll i){return f[i]+b(i)*b(i);}

 

      可以看见的是,对于每一个确定的$i$值,斜率的值是一个常数,截距为$f_{i}-a_{i}^{2}$,我们的目的是求$f_{i}$的最小值,即截距的最小值.所以状态的转移相当于用一条斜率固定的直线在笛卡尔坐标系内上下平移,于是接下来我们可以进行线性规划.

      基于数形结合思想,我们先来张图片分析一下.

  

 

 

 

 

     由于凸包相邻两点斜率单调递增,而每条直线的斜率$2\cdot a_{i}$也是单调递增的.又由图像可知,我们要求的即满足斜率$slope>2\cdot a_{i}$的斜率最小直线,所以可以用单调队列维护这个凸包.具体实现如下:

  1.当我们要状态转移时,依题意,找到第一个$slope(P_{head},P_{head+1})>slope{2\cdot a_{i}}$的点然后转移即可.  

        $$while(slope(P[head],P[head+1)<=2*a(i))++head$$

  2.当我们要插入一个点时,由于凸包斜率单调递增,所以应从队尾将$slope(P_{tail-1},P_{tail})<slope(P_{tail-1},P_{i}) $的点统统弹出,然后插入即可.

    $$while(slope(P[tail-1],P[tail])>slope(P[tail-1],P[i])tail--$$

code:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define R register
#define next xrzzxtzjyzxxyjljcmlg
#define debug puts("mlg")
using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
inline ll read();
inline void write(ll x);
inline void writesp(ll x);
inline void writeln(ll x);
ll n,L;
double sum[100000],f[100000];
ll head,tail,q[100000];
inline double a(ll i){return sum[i]+i;}
inline double b(ll i){return a(i)+L+1;}
inline double X(ll i){return b(i);}
inline double Y(ll i){return f[i]+b(i)*b(i);}
inline double slope(ll i,ll j){return (Y(j)-Y(i))/(X(j)-X(i));}
int main(){
    n=read();L=read();
    for(R ll i=1;i<=n;i++)sum[i]=read(),sum[i]+=sum[i-1];
    head=tail=1;
    for(R ll i=1;i<=n;i++){
        while(head<tail&&slope(q[head],q[head+1])<2*a(i)) ++head;
        f[i]=f[q[head]]+(a(i)-b(q[head]))*(a(i)-b(q[head]));
        while(head<tail&&slope(i,q[tail-1])<slope(q[tail-1],q[tail])) --tail;
        q[++tail]=i;
    }
    writeln((ll)f[n]);
}
inline ll read(){ll x=0,t=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') t=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*t;}
inline void write(ll x){if(x<0){putchar('-');x=-x;}if(x<=9){putchar(x+'0');return;}write(x/10);putchar(x%10+'0');}
inline void writesp(ll x){write(x);putchar(' ');}
inline void writeln(ll x){write(x);putchar('\n');}

  斜率优化dp对着题解肝了一个晚上才搞明白,现在仍十分不熟练,循着题解作此总结,希望能更熟悉.

                        醉卧沙场君莫笑,古来征战几人回?

 

 

  

posted @ 2020-07-23 06:57  月落乌啼算钱  阅读(117)  评论(0编辑  收藏  举报