对于一些具有决策单调性的dp题目,我们可以应用斜率优化将复杂度从O(n^2)降到O(n)。
bzoj1010 HNOI2008 玩具装箱toy
题目大意:对于一些一维长度的物品,我们可以将连续的i~j个物品放在一起,费用是(j-i+sigma lk(i<=k<=j)-L)^2,求n个物品最小费用。
思路:对于这类dp题目,我们可以通过进行一系列数学化简,找出其中的斜率,进而斜率优化。我们设sum[i]表示前i件物品的长度和,s[i]为sum[i]+i,dp[i]=dp[j]+(s[i]-s[j]-L)^2。我们取j<k,dp[j,i]-dp[k,i]>0,带入之前的式子,将L+1,将含i的移到右边,含j、k的移到另一边,就会得到((dp[k,i]+sk^2)-(dp[j,i]+sj^2))/(sk-sj)<2(si-L)①,我们设左边=w[k,j],显然当w[k,j]<右边的时候,k更优。对于j<k<i,w[i,k]<w[k,j]②时,k不可能是之后用到的最优解,所以可以舍弃。我们维护可能用到的最优解,不断的舍弃那些不好的解,就能将复杂度降至O(n)。对于这里涉及两个符号的问题,我们可以统一符号(如上两个式子)。
#include<iostream> #include<cstdio> #include<cstring> #define maxnode 50005 using namespace std; long long dp[maxnode]={0},sum[maxnode]={0},q[maxnode]={0},l; long long getup(int j,int k) { return dp[j]+(sum[j])*(sum[j])-(dp[k]+(sum[k])*(sum[k])); } long long getdown(int j,int k) { return (sum[j]-sum[k]); } long long getdp(int i,int j) { return dp[j]+(sum[i]-sum[j]-l)*(sum[i]-sum[j]-l); } double g(int j,int k) { return getup(j,k)*1.0/(2.0*getdown(j,k)); } int main() { int n,i,j,head,tail; scanf("%d%lld",&n,&l); for (i=1;i<=n;++i) { scanf("%lld",&sum[i]); sum[i]+=sum[i-1]; } for (i=1;i<=n;++i) sum[i]+=(long long)i; head=tail=1;q[tail]=0;++l; for (i=1;i<=n;++i) { while(head<tail&& g(q[head+1],q[head]) < 1.0*(sum[i]-l)) ++head; dp[i]=getdp(i,q[head]); while(head<tail&& g(i,q[tail]) < g(q[tail],q[tail-1])) --tail; q[++tail]=i; } printf("%lld\n",dp[n]); }