[HNOI2008][BZOJ1010] 玩具装箱toy - 斜率优化

1010: [HNOI2008]玩具装箱toy

Time Limit: 1 Sec   Memory Limit: 162 MB
Submit: 8231   Solved: 3239
[ Submit][ Status][ Discuss]

Description

P教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京。他使用自己的压缩器进行压缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中。P教授有编号为1...N的N件玩具,第i件玩具经过压缩后变成一维长度为Ci.为了方便整理,P教授要求在一个一维容器中的玩具编号是连续的。同时如果一个一维容器中有多个玩具,那么两件玩具之间要加入一个单位长度的填充物,形式地说如果将第i件玩具到第j个玩具放到一个容器中,那么容器的长度将为 x=j-i+Sigma(Ck) i<=K<=j 制作容器的费用与容器的长度有关,根据教授研究,如果容器长度为x,其制作费用为(X-L)^2.其中L是一个常量。P教授不关心容器的数目,他可以制作出任意长度的容器,甚至超过L。但他希望费用最小.

Input

第一行输入两个整数N,L.接下来N行输入Ci.1<=N<=50000,1<=L,Ci<=10^7

Output

输出最小费用

Sample Input

5 4
3
4
2
1
4

Sample Output

1

HINT

Source

       首先我们应该考虑简单的DP,设dp[i]表示将前i个物品放入箱子的最小答案,sum[i]表示前i个物品的长度和,那么我们可以得到一个下面这样的柿子:
            dp[i]=Min{dp[j]+(sum[i]-sum[j]+i-j-1-l)^2} (j=0...i-1) 
(看不懂的请自行面壁)
       从而推出了这样的程序:
#include "stdio.h"
#include "iostream"
using namespace std;
const int N=50005;
typedef long long ll;
const ll INF=1ll<<62;
ll n,l;
int len[N];
ll dp[N],sum[N];
//dp[i]表示将前i个物品放入箱子的最小答案 
int main(){
    freopen("toy.in","r",stdin);
    freopen("toy.out","w",stdout);
    cin>>n>>l;
    int i,j;
    for (i=1; i<=n; i++)
        scanf("%d",len+i), sum[i]=sum[i-1]+len[i], dp[i]=INF;
    for (i=1; i<=n; i++)
        for (j=0; j<i; j++){
            int q=(sum[i]-sum[j]+i-j-1-l);
            dp[i]=min(dp[i],dp[j]+q*q);
        }
    cout<<dp[n];
    return 0;
}

       可是我们发现,题目中有着这样的一个限制:1<=N<=50000 这样的话,如果我们采取刚刚那种方法,时间复杂度为O(n^2)。很显然,在这样的题目中O(n^2)的算法无能为力。
       因此我们很明显地需要更加优化的算法。那么我们考虑两个位置i和j,且i<j,对于i位置有一个最优决策k和另一个决策t,且k<t。且f[j]=f[i]+v。
       我们这里用f[i]代替sum[i]+i,c代表1+l。现在想要证明决策单调性,就要证明
             dp[t]+(f[j]-f[t]-c)^2<=dp[k]+(f[j]-f[k]-c)^2
       即证dp[t]+(f[i]-f[t]-c+v)^2<=dp[k]+(f[i]-f[k]-c+v)^2
       即证dp[t]+2*v*(f[i]-f[t]-c)<=dp[k]+2*v*(f[i]-f[k]-c)
       即dp[t]-2*v*f[t]<=dp[k]-2*v*f[k]
       即2*v*(f[t]-f[k])>dp[t]-dp[k]
       显然正确
(具体证明请参看hzwer的blog)
       因此我们维护一个单调队列,在这里称作为凸壳。那么这个单调队列在什么情况下删减呢?显然有两种情况:(1) 前面的答案已经不是最优 (2)后面的答案已经没有必要保留
       判断前面的答案是不是最优只要解决问题(2)就毫无难度,所以现在我们看问题(2):什么时候队尾没有必要保留呢?
       现在我们假设两个决策k和j,且k决策更优,则有 dp[k]+(f[i]-f[k]-c)^2<=dp[j]+(f[i]-f[j]-c)^2,展开可得:

              (dp[k]+(f[k]+c)^2-dp[j]-(f[j]+c)^2)/2*(f[k]-f[j])<=f[i]

       因为f[i]单调递增,所以对于队尾,我们维护一个凸壳slop,如果队尾的两个决策的斜率比最后一个决策和新的决策之间的斜率还要大的话,就要删除队尾了,已经没有存在的必要。附代码:

#include "stdio.h"
#include "iostream"
#include "stdlib.h"
#include "algorithm"
using namespace std;
typedef long long ll;
const int N=50005; 
int n,l[N];
typedef long long ll;
ll s[N],f[N],L;
int fr,bk,q[N];
inline ll slop(int u,int v){
       ll x=s[v]-s[u-1]+v-u-L;
       if(x<0)x=-x;
       return x*x;
}
inline ll sol0(int k){
       ll a=s[k]+k+L+1;
       return a*a+f[k];
}
inline bool slop0(int a,int b,int c){
       double jc0=(sol0(b)-sol0(a))/(2.0*(s[b]-s[a]+b-a));
       double jc1=(sol0(c)-sol0(b))/(2.0*(s[c]-s[b]+c-b));
       return jc0>=jc1;
}
int main()
{
    cin>>n>>L;
    int i;
    for (i=1;i<=n;i++){
        scanf("%d",l+i);
        s[i]=s[i-1]+l[i];
    }
    for (i=1,fr=1,bk=1,q[1]=0;i<=n;i++){
        while(fr<bk&&f[q[fr]]+slop(q[fr]+1,i)>f[q[fr+1]]+slop(q[fr+1]+1,i))
         fr++;
        f[i]=f[q[fr]]+slop(q[fr]+1,i);
        while(fr<bk&&slop0(q[bk-1],q[bk],i))
         bk--;
        q[++bk]=i;
    }
    cout<<f[n]<<endl;
    return 0;
}


你可能感兴趣的:([HNOI2008][BZOJ1010] 玩具装箱toy - 斜率优化)