2019牛客暑期多校训练营(第十场) J.Wood Processing(斜率dp)

题目

n(n<=5e3)块木板,

每块木板有两个参数,w(1<=w<=1e7)和h(1<=h<=1e7),木板不能旋转,

两块木板拼接的时候,高度以矮的那块木板为准,把高的那块截掉一部分浪费掉,

然后拼接起来,新的木板为w1+w2,高度为min(h1,h2),

问将n块拼接成k块木板所要浪费的最小面积是多少

思路来源

https://blog.youkuaiyun.com/weixin_43823767/article/details/99849399

https://www.cnblogs.com/Xing-Ling/p/11210179.html

题解

考虑1 2 3 4四块木板,不难证明h降序相邻取比h乱序相邻取时更优,

即1>2>3>4时,两块木板应该高度分别是2 4,而乱序如1 3 2 4 时只能为3 4

所以按高度排降序,dp[i][j]表示把前i块木板拼成j块木板的最小浪费代价,

划分最后一段,dp[i][j]=min(dp[k][j-1]+[k,i]拼成一块木板)

 

对于i来说,计把前i块拼在一起浪费的面积为s[i],w的前缀和为sw[i],

dp[i][j]=min(dp[k][j-1]+(s[i]-s[k])-sw[k]*(p[k].h-p[i].h)

对于两个决策点k_{1}<k_{2},显然s[k_{1}]<s[k_{2}]sw[k_{1}]<sw[k_{2}]

 

dp[k_{1}][j-1]+(s[i]-s[k_{1}])-sw[k_{1}]*(p[k_{1}].h-p[i].h)

>=dp[k_{2}][j-1]+(s[i]-s[k_{2}])-sw[k_{2}]*(p[k_{2}].h-p[i].h)

成立,

说明k1不如k2优,

 

则有,

sw[k_{1}]*p[i].h-sw[k_{2}]*p[i].h+s[k_{2}]-s[k_{1}]

<=dp[k_{2}][j-1]-dp[k_{1}][j-1]+sw[k_{1}]*p[k_{1}].h-sw[k_{2}]*p[k_{2}].h

成立,

 

计f(k,j-1)=dp[k][j-1]-sw[k]*p[k].h-s[k],f(k,j-1)=dp[k][j-1]-sw[k]*p[k].h-s[k],

则有(sw[k_{1}]-sw[k_{2}])*p[i].h>=f(k_{2},j-1)-f(k_{1},j-1)成立,

等价于-p[i].h>=\frac{f(k_{1},j-1)-f(k_{2},j-1)}{sw[k_{1}]-sw[k_{2}]}

此时应该忽略k1这个解,双端队列不断单增

 

此外,无论何时,双端队列尾端应该维护一个下凸壳,

这样的形状,

而不能存在以下形状,即上凸壳的形状,

最后dp的取点,等价于,斜率由ki来定时,使得b最小的取值点

不难发现,无论用斜率为何值的直线去切上凸壳,

k2都不能成为使截距最低的最优点,所以k2不能存在在凸壳中

即队列尾类似维护凸包不断弹栈的过程,

具体可参考思路来源https://www.cnblogs.com/Xing-Ling/p/11210179.html

 

此外,如果不能保证1到n的斜率k(本题中为-p[i].h)单调,

则需在凸包上二分那个合适的切点i,

使得(i-1,i)的斜率小于k,而(i,i+1)的斜率大于k

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e3+10,K=2e3+10,mod=1e9+7;
struct P{
    ll w,h;
    bool operator<(const P &x)const{
        return h>x.h;
    }
}p[N];
int n,k,h,t;
ll s[N],sw[N];//S的前缀和 W的前缀和 用于求浪费的面积
ll dp[N][K],q[N];
double slope(int i,int j,int t){
    ll x1=sw[i],x2=sw[j];
    ll y1=dp[i][t]-sw[i]*p[i].h-s[i];
    ll y2=dp[j][t]-sw[j]*p[j].h-s[j];
    return double(y2-y1)/(x2-x1);
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;++i){
        scanf("%lld%lld",&p[i].w,&p[i].h);
    }
    sort(p+1,p+n+1);
    for(int i=1;i<=n;++i){
        s[i]=s[i-1]+sw[i-1]*(p[i-1].h-p[i].h);
        sw[i]=sw[i-1]+p[i].w;
        dp[i][1]=s[i];
    }
    for(int j=2;j<=k;++j){
        h=0;t=-1;//q[0]=0
        for(int i=1;i<=n;++i){
            while(h<t && slope(q[h],q[h+1],j-1)<=-p[i].h){
                h++;
            }
            int pos=q[h];
            dp[i][j]=dp[pos][j-1]+(s[i]-s[pos])-sw[pos]*(p[pos].h-p[i].h);
            while(h<t && slope(q[t-1],q[t],j-1)>=slope(q[t-1],i,j-1)){
                t--;
            }
            q[++t]=i;
        }
    }
    printf("%lld\n",dp[n][k]);
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值