题目
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],
对于两个决策点,显然
,
若
成立,
说明k1不如k2优,
则有,
成立,
计
则有成立,
等价于,
此时应该忽略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;
}