如果你和我一样觉得一维 DP 的转移很难想,不妨考虑二维 DP。
设 \(dp_{i,j}\) 表示摆放前 \(i\) 本书,当前最后一层恰好放了 \(j\) 本书时,所有书架的总最小高度。
然后你就会发现有一个条件没有放进状态里面:
每层书架的总宽度不能超过 \(L\)。
所以再开一个 \(dp2\) 数组,\(dp2_{i,j}\) 表示前 \(i\) 本书、最后一层放 \(j\) 本书时,当前最后一层的总宽度。
然后考虑转移。设现在推 \(dp_{i,j}\) 和 \(dp2_{i,j}\)。
分情况讨论:
- 如果 \(j = 1\),说明我开了新的一层,所以 \(dp_{i,j}\) 从 \(dp_{i-1,1}\) 到 \(dp_{i-1,i-1}\) 枚举一遍求最小值,然后加上 \(h_{i}\)。\(dp2_{i,j}\) 直接赋值为 \(w_{i}\)。
- 否则,我一定放在上一层末尾。
- 如果上一层容不下我,那么当前状态无效,退出内层循环(因为 \(j\) 递增时,同层书本数越多,总宽度一定单调递增,所以可以中断循环);
- 否则,我可以塞进去。于是,当前层的高度一定是我的高度和当前层其他书的高度的最大值。
当前层放下我之前的第一本书是第 \(i-1-(j-1)+1 = i-j+1\) 本,
在我塞进去之前,当前层的最大值就是区间 \([h_{i-j+1},h_{i-1}]\) 的最大值,
在我塞进去之后,当前层的最大值就是区间 \([h_{i-j+1},h_{i}]\) 的最大值。
为了减少讨论情况,\(dp_{i,j}\) 在 \(dp_{i-1,j-1}\) 的基础上直接减去区间 \([h_{i-j+1},h_{i-1}]\) 的最大值,再加上区间 \([h_{i-j+1},h_{i}]\) 的最大值就行了。\(dp2_{i,j}\) 的值为 \(dp2_{i-1,j-1} + w_{i}\)。
RMQ 用 ST 表或线段树都行,数据不卡常。如果使用线段树,整体时间复杂度 \(O(n^2 \log n)\)。如果使用 ST 表,整体时间复杂度 \(O(n^2)\)。
上代码,我用线段树写的。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
#define div() cout << "-----------------------\n";
#define debug(n) cout << #n << " = " << n << "\n";
#define deb(n) cout << #n << " = " << n << " ";
struct Tree{
int l,r,maxn;
}tree[8005];
int n,L;int dp[2005][2005],dp2[2005][2005];int w[2005],h[2005];int ans = 1e18;
inline void build(int i,int l,int r){
tree[i].l = l;tree[i].r = r;
if(l == r){tree[i].maxn = h[l];return;}
int mid = (l+r) >> 1;
build(2*i,l,mid);
build(2*i+1,mid+1,r);
tree[i].maxn = max(tree[2*i].maxn,tree[2*i+1].maxn);
}
inline int query(int i,int l,int r){
if(tree[i].l >= l && tree[i].r <= r){return tree[i].maxn;}
int mid = (tree[i].l + tree[i].r) >> 1;int res = 0;
if(l <= mid){res = max(res,query(2*i,l,r));}
if(r > mid){res = max(res,query(2*i+1,l,r));}
return res;
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin >> n >> L;
for(int i = 1;i <= n;i++){cin >> h[i] >> w[i];}
build(1,1,n);
memset(dp,0x3f,sizeof(dp));//dp初始化为极大值
dp2[1][1] = w[1];dp[1][1] = h[1];
for(int i = 2;i <= n;i++){
// 情况1:新开一层
dp2[i][1] = w[i];
//枚举到i-1就行了
for(int j = 1;j <= i-1;j++){dp[i][1] = min(dp[i][1],dp[i-1][j]+h[i]);}
// 情况2:放在当前层末尾
for(int j = 2;j <= i;j++){
if(dp2[i-1][j-1]+w[i] <= L){
dp[i][j] = dp[i-1][j-1]-query(1,i-j+1,i-1)+query(1,i-j+1,i);
dp2[i][j] = dp2[i-1][j-1]+w[i];
}
else {break;}
}
}
//注意最高层可能有1~n本书
for(int i = 1;i <= n;i++){ans = min(ans,dp[n][i]);}
cout << ans;
return 0;
}
AC 本题后建议移步 P5124。
浙公网安备 33010602011771号