大视野在线测评 1010 [HNOI2008]玩具装箱toy (动态规划)

题目链接

1010: [HNOI2008]玩具装箱toy

Time Limit: 1 Sec   Memory Limit: 162 MB
Submit: 7027   Solved: 2670
[ 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

题解:用dp[i]表示装完前i个物品的最小花费,转移就是:

dp[i]=min(dp[k]+(sum[i]-sum[k]+i-k-1-l)*(sum[i]-sum[k]+i-k-1-l))

sum[i]表示前i个物品的前缀和。

方法一:

另w(k,i)=(sum[i]-sum[k]+i-k-1-l)*(sum[i]-sum[k]+i-k-1-l);

可以证明:w(i,j)+w(i+1,j+1)<=w(i,j+1)+w(i+1,j)。

另k(i)表示状态i取到最优值时的决策,则:k(i)<=k(j)。决策单调。

因此我们可以采用论文:《1D/1D动态规划优化初步》中的方法。

O(nlgn)解决此问题。代码如下:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<map>
#include<string.h>
#include<vector>
#include<math.h>
typedef long long LL;
typedef unsigned long long LLU;
const double eps=1e-8;
const int nn=110000;
const int inf=0x3fffffff;
using namespace std;
int n;
LL l,c[nn];
LL dp[nn];
LL sum[nn];
struct node
{
    int x,y;
    int val;
    node(){}
    node(int xx,int yy,int vall)
    {
        x=xx,y=yy,val=vall;
    }
};
node que[nn];
int head,last;
LL solve(int i,int k)
{
    return dp[k]+(sum[i]-sum[k]+i-k-1-l)*(sum[i]-sum[k]+i-k-1-l);
}
bool check(int id,int v1,int v2)
{
    return solve(id,v1)<solve(id,v2);
}
void add(int x,int y,int val)
{
    node tem=node(x,y,val);
    while(last>head)
    {
        if(check(que[last-1].x,que[last-1].val,val))
        {
            int l=que[last-1].x+1,r=que[last-1].y+1;
            while(l<r)
            {
                int mid=(l+r)/2;
                if(check(mid,que[last-1].val,val))
                {
                    l=mid+1;
                }
                else
                    r=mid;
            }
            tem.x=r;
            que[last-1].y=r-1;
            break;
        }
        else
        {
            tem.x=que[last-1].x;
            last--;
        }
    }
    if(tem.x<=n)
    {
        tem.y=n;
        que[last++]=tem;
    }
}
int main()
{
    int i;
    while(scanf("%d%lld",&n,&l)!=EOF)
    {
        sum[0]=0;
        for(i=1;i<=n;i++)
        {
            scanf("%lld",&c[i]);
            sum[i]=sum[i-1]+c[i];
        }
        dp[0]=0;
        head=last=0;
        add(1,n,0);
        for(i=1;i<=n;i++)
        {
            while(que[head].y<i)
            {
                head++;
            }
            dp[i]=solve(i,que[head].val);
            add(n+1,n+1,i);
        }
        printf("%lld\n",dp[n]);
    }
    return 0;
}

方法二:再来分析转移方程:

dp[i]=min(dp[k]+(sum[i]-sum[k]+i-k-1-l)*(sum[i]-sum[k]+i-k-1-l))

       =min(dp[k]+(sum[i]+i-l-1)*(sum[i]+i-l-1)+(sum[k]+k)*(sum[k]+k)-2*(sum[i]+i-l-1)*(sum[k]+k))

       =min( dp[k]+(sum[k]+k)*(sum[k]+k)-2*(sum[i]+i-l-1)*(sum[k]+k) ) + (sum[i]+i-l-1)*(sum[i]+i-l-1)

我们令常量 P=(sum[i]+i-l-1)

令:X(k)=dp[k]+(sum[k]+k)*(sum[k]+k)

令:Y(k)=(sum[k]+k)

则:dp[i]=min(X(k)-2*P*Y(k))+p*p;

令C=X(k)-2*P*Y(k) ,则:

Y(k)=1/2p*X(k) - C/2*p;

我们可以把决策K看成是二维坐标上的一个点( X(k),Y(k) ) ,求解dp[i]可以看成是,一条斜率固定的直线,在一些点中(决策),找一个点,使得纵坐标的截距最大即(C最小)。不难发现,这个点一定在这些点的凸包上。

由于1/2*p随着i的增加单调递减,X(k)随着K的增加单调递增。

因此我们可以用一个双端队列维护凸包上的点,队列中相邻两点的斜率随着x的增加而减少。

每次转移的时候,从队首开始,找到第一个与后一个点的斜率小于当前直线的斜率的点,就是我们要找的决策点。对于不满足条件的点出队。

转移完成以后,从队尾开始添加一个点,保证队列中的点斜率递减,不满足条件的点出队。

这样就可以均摊O(1)复杂度,实现转移。

对于斜率优化的具体内容可看论文:《1D/1D动态规划优化初步》,或者其它关于斜率优化的论文。

代码如下:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<math.h>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<vector>
#include<string.h>
#include<string>
#include<stdlib.h>
typedef long long LL;
typedef unsigned long long LLU;
const int nn=51000;
const int inf=0x3fffffff;
const LL inf64=(LL)inf*inf;
const int mod=1000000007;
using namespace std;
int n;
LL l,c[nn];
LL dp[nn];
LL sum[nn];
pair<LL,LL>que[nn];
int head,last;
void add(LL x,LL y)
{
    while(last-1>head)
    {
        if((que[last-1].second-que[last-2].second)*(x-que[last-1].first)>(que[last-1].first-que[last-2].first)*(y-que[last-1].second))
            break;
        last--;
    }
    que[last++]=make_pair(x,y);
}
int main()
{
    int i;
    while(scanf("%d%lld",&n,&l)!=EOF)
    {
        sum[0]=0;
        for(i=1;i<=n;i++)
        {
            scanf("%lld",&c[i]);
            sum[i]=sum[i-1]+c[i];
        }
        dp[0]=0;
        head=last=0;
        add(0,0);
        LL ix;
        for(i=1;i<=n;i++)
        {
            ix=sum[i]+i-l-1;
            while(head<last-1)
            {
                if((que[head+1].first-que[head].first)>(que[head+1].second-que[head].second)*ix*2)
                {
                    break;
                }
                head++;
            }
            dp[i]=ix*ix+que[head].first-2*ix*que[head].second;
            add(dp[i]+(sum[i]+i)*(sum[i]+i),sum[i]+i);
        }
        printf("%lld\n",dp[n]);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值