解题:HNOI 2008 玩具装箱

题面

搞了一晚上斜率优化,大概懂了一点,写写

原来常用的优化dp方法:做前缀和,预处理,数据结构维护

现在有转移方程长这样的一类dp:$dp[i]=min(dp[i],k[i]*x[j]+y[j]+c[i]+a)$,其中$c[i],k[i],x[j],y[j]$都是关于$i$或者$j$的变量,在$i$或者$j$确定时不变,$a$是个常量

然后发现$x[j]$带着一个$k[i]$的系数,不好优化

从另一个角度考虑,想想高中老师教给我们的线性规划

可以发现因为对于每次转移的$i$来说$c[i],k[i]$都不变,我们可以把$k[i]*x[j]+y[j]$看做是一条直线(初中的一次函数),$k[i]$是斜率,然后$x[j]$是横坐标,$y[j]$是纵坐标,别的都是关于$i$的变量或者常量,不用管。那么实际上我们求的$dp$数组的最终结果就是这条直线的截距的最值(初中的与y轴的交点)

然后我们发现发现对于每个下标我们都可以依照上面的$x[j],y[j]$把它表示成平面上的一个点$(x,y)$,然后这些点会形成一个点集,根据线性规划的知识,可以发现根据斜率的正/负我们的最优决策点都在下/上凸包上,于是可以优化了

当我们每次转移用到的斜率单调时,直接用单调队列维护凸包,先把斜率大/小的都踢掉,转移之后再把现在不在凸包上的点也都踢掉,最后把当前点加进去

当我们每次转移用到的斜率不单调时,就不能根据斜率直接踢了,但仍然用单调队列维护凸包,只是把找最优决策点用二分代替

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int N=50005;
 6 long long len[N],dp[N],que[N],n,x,f,b;
 7 inline long long c1(int p){return len[p]+p;}
 8 inline long long c2(int p){return c1(p)+x+1;}
 9 inline long long K(int p){return 2*c1(p);}
10 inline long long X(int p){return c2(p);}
11 inline long long Y(int p){return c2(p)*c2(p)+dp[p];}
12 inline long long S(int a,int b){return (double)(Y(b)-Y(a))/(double)(X(b)-X(a));}
13 int main()
14 {
15     scanf("%lld%lld",&n,&x);
16     for(int i=1;i<=n;i++)
17         scanf("%lld",&len[i]),len[i]+=len[i-1];
18     que[f=b=0]=0;
19     for(int i=1;i<=n;i++)
20     {
21         while(b-f>=1&&S(que[f],que[f+1])<K(i)) f++;
22         dp[i]=dp[que[f]]+(c1(i)-c2(que[f]))*(c1(i)-c2(que[f]));
23         while(b-f>=1&&S(que[b-1],i)<S(que[b],que[b-1])) b--; que[++b]=i;
24     }
25     printf("%lld",dp[n]);
26     return 0;
27 }
View Code
原文地址:https://www.cnblogs.com/ydnhaha/p/10004299.html