洛谷 2120 仓库建设(详解)

仓库建设

终于有时间写写博客了!

听说这题是NOI/CTSC的哇!

激动!先看题意!

Question

很熟悉吧,这是一道动态规划的题:若要求从1~N的货物全部都安置的最小费用,就要用1~N-1的最小费用去更新……这就转化为了子问题的求解典型的DP。

根据子问题的定义,我们可以定义DP方程:

dp[ i ]表示在i这个地方建设仓库并且将 i 之前的所有货物都处理完的最小费用值。

由于货物只能往山下运送,那么也就是说 k 处的货物只能向 k~N 运送安置,可以枚举 j ——上一个建设仓库的地方,那么从 j+1到 i 这一段都将要安置到在 i 这个地方建设的仓库中,费用为 sigma(j+1->i) {P[k]*(X[i]-X[k])} 。

得到DP方程: 

dp[ i ]=min{ ( dp[ i ] , dp[ j ]+ sigma(k=j+1->i) { P[k]*(X[i] - X[k])}) }


枚举 i 和 j 复杂度近似O( n^2 ),而计算费用的和需要O( n ),那么总复杂为O( n^3 ),爆炸!


我们可以将式子整理一下:

sigma(k=j+1->i) { P[k]*(X[i] - X[k])}) = X[ i ]*sigma( k=j+1->i ){ P[k] } - sigma( k=j+1->i ){ P[k]*X[k] }

看出来了吗,这样我们就不需要for一遍去求安置费用了,我们可以记录两个前缀和:

1、sum1[ i ]:sum1[ i ]=sum1[ i - 1 ]+P[ i ]

2、sum2[ i ]:sum2[ i ]=sum2[ i - 1 ]+P[ i ]*X[ i ]

那么全新的方程:

dp[ i ] = dp[ pos ]+x[ i ]*(sum1[ i ]-sum1[ pos ])-(sum2[ i ]-sum2[ pos ])+C[ i ],其中pos表示最优位置

这样复杂度降到了O( n^2 ),然而并没有卵用……


进行斜率优化:

假设 k>g,而且 k 处建设上一个仓库的费用比在 g 建设上一个仓库的费用优(小):

dp[ k ]+x[ i ]*(sum1[ i ]-sum1[ k ])-(sum2[ i ]-sum2[ k ])+C[ i ] —— 1式

dp[ g ]+x[ i ]*(sum1[ i ]-sum1[ g ])-(sum2[ i ]-sum2[ g ])+C[ i ] ——2式

由于 k 比 g 优,那么 1式 <= 2式!

整理一下,可以将式子中相同的都抵消掉,得到优化方程,即满足 k 比 g 优时的式子:

(dp[ k ]-dp[ j ]+sum2[ k ]-sum2[ j ]) / (sum1[ k ]-sum1[ j ]) <= X[ i ],我们把不等式左边记为斜率


维护一个凸包(斜率递增),并且使用队列存储符合凸包的点,那么每一次碰到新的 i 时,我们只需要比较X[ i ]和队首和队首+1的形成的斜率的值(k=head+1 , g=head),如果小于X[ i ],那么说明head的费用一定不比head+1优!这时我们可以将head删掉,将head+1作为新的队首;否则说明当前 i 下,head比head+1优,不删。注意,需要保证队列之中有东西


最后得到的队首一定是对于当前 i 来说一定是最优的,那么用之更新!

这时候就入队吗?不!

更新完毕,我们还是需要维护一个凸包,也就是队列尾部:tail 以及 tail-1 和将要入队的 i 必须保证:

tail与tail-1构成的斜率小于tail与i构成的斜率!


好了,这就可以将 i 入队了!


最后的答案就是dp[ N ]


AC


下面放程序:

#include<bits/stdc++.h>

const int N=1000000+5;

using namespace std;

long long dp[N],sum1[N],sum2[N],x[N],p[N],c[N];
int n,h,t,q[N<<4];

double slope(int j,int k){
    return (double)(dp[k]-dp[j]+sum2[k]-sum2[j])/(double)(sum1[k]-sum1[j]);
}

int main(){
    
    ios::sync_with_stdio(false);
    
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>x[i]>>p[i]>>c[i];
    
    for(int i=1;i<=n;i++){
        sum1[i]=sum1[i-1]+p[i];
        sum2[i]=sum2[i-1]+p[i]*x[i];
    }
    
    for(int i=1;i<=n;i++){
        while(h<t && slope(q[h],q[h+1])<=x[i])
            h++;
        int tmp=q[h];
        dp[i]=dp[tmp]+x[i]*(sum1[i]-sum1[tmp])-(sum2[i]-sum2[tmp])+c[i];
        while(h<t && slope(q[t-1],q[t])>slope(q[t],i))
            t--;
        q[++t]=i;
    }
    cout<<dp[n];
    
    return 0;
}

呼!OK,返校!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值