题目链接:https://ac.nowcoder.com/acm/contest/890/J
题目大意:
给一群木头,有长和宽,然后一群木头拼起来就是以最低的那个为标准,高于他的全部剪掉,问拼出k块最少浪费多少木头。
题目思路:
分成k段,每段取最小的价值我们肯定要对木头从低到高进行排序。
分成k段每段最低的也就是最靠前的那个,全部的木头都要砍成这么低。
我们可以写一个n*k的dp转移方程。
dp[i][j] 表示前i块木头分成 j 段最小的浪费值。
sum_s[i]:排好序后,前 i 块木头的面积和。
sum_w[i]: 排好序后,前 i 块木头的宽度和。
发现转移方程是n*n*k的需要优化,会发现这个题把方程拆开并移项之后,成为下式。
sum_w[i] : 作为斜率,满足单调不减性。
sum[j][k-1]-sum_s[j]+sum_w[j]*h[j+1] : 作为函数值。
h[j+1]: 作 自变量x
(dp[i][k]-sum_s[i]) : 作截距
注意转移的时候dp【i】【1】要自己初始化,不可以直接转移因为dp【i】【0】是非法状态。
函数值显然具有大于等于0的性质,所以可以使用斜率dp优化处理,从而实现O(1)转移。
最后复杂度O(n*k)
极限数据可以发现在斜率交叉相乘的时候会爆ll 所以需要__int128
#include<bits/stdc++.h>
#define ll __int128
using namespace std;
const ll MAXN = 5005;
ll dp[MAXN][2005];
ll sum_s[MAXN],sum_w[MAXN];
ll que[MAXN];
struct node
{
long long w,h;
}C[MAXN];
bool cmp(node a,node b)
{
return a.h<b.h;
}
ll gets(ll i,ll j,ll k)
{
return (dp[i][k]-sum_s[i]+sum_w[i]*C[i+1].h)-(dp[j][k]-sum_s[j]+sum_w[j]*C[j+1].h);
}
ll getm(ll i,ll j)
{
return C[i+1].h-C[j+1].h;
}
inline void out(ll x){
if(x>9) out(x/10);
putchar(x%10+'0');
}
int main()
{
int n,k;
cin>>n>>k;
for(ll i=1;i<=n;i++){
cin>>C[i].w>>C[i].h;
}
sort(C+1,C+1+n,cmp);
for(ll i=1;i<=n;i++){
sum_s[i] = sum_s[i-1]+C[i].w*C[i].h;
sum_w[i] = sum_w[i-1]+C[i].w;
}
for(ll i=1;i<=n;i++){
dp[i][1]=sum_s[i]-sum_w[i]*C[1].h;
//cout<<dp[i][1]<<endl;
}
for(ll j=2;j<=k;j++){
ll L = 0,R = -1;
que[++R] = j-1;
for(ll i=j;i<=n;i++){
while(L < R && gets(que[L+1],que[L],j-1) <= sum_w[i]*getm(que[L+1],que[L]) )L++;
ll now = que[L];
dp[i][j] = dp[now][j-1] + (sum_s[i]-sum_s[now])-(sum_w[i]-sum_w[now])*C[now+1].h;
while(L < R && gets(que[R],que[R-1],j-1)*getm(i,que[R]) >= gets(i,que[R],j-1) * getm(que[R],que[R-1]) )R--;
que[++R]=i;
}
}
out(dp[n][k]);
puts("");
//cout<<dp[n][k]<<endl;
}